Merge pull request #647 from KeyboardNerd/spkg/cvrf
vulnsrc: Refactor debian and alpine sourcesmaster
commit
05cbf328aa
@ -0,0 +1,66 @@
|
||||
// Copyright 2018 clair 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 fsutil contains utility functions for file system querying.
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dirFilter int
|
||||
|
||||
const (
|
||||
// All doesn't filter anything
|
||||
All dirFilter = iota
|
||||
// FilesOnly filters Dir function to return only files
|
||||
FilesOnly
|
||||
// DirectoriesOnly filters Dir function to return only directories
|
||||
DirectoriesOnly
|
||||
)
|
||||
|
||||
// Readdir lists the files or folders under the given path and filter based on the
|
||||
// dirFilter.
|
||||
func Readdir(path string, filter dirFilter) ([]string, error) {
|
||||
dir, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer dir.Close()
|
||||
finfos, err := dir.Readdir(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var files []string
|
||||
for _, info := range finfos {
|
||||
if filter == DirectoriesOnly && !info.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if filter == FilesOnly && info.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(info.Name(), ".") {
|
||||
continue
|
||||
}
|
||||
|
||||
files = append(files, info.Name())
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
@ -1,8 +1,18 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- "1.10"
|
||||
- tip
|
||||
before_install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get github.com/golang/lint/golint
|
||||
script:
|
||||
- go test -v -covermode=count -coverprofile=coverage.out
|
||||
- go vet ./...
|
||||
- test -z "$(gofmt -d -s . | tee /dev/stderr)"
|
||||
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
|
||||
|
@ -0,0 +1,38 @@
|
||||
// +build go1.7
|
||||
|
||||
package httprouter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type paramsKey struct{}
|
||||
|
||||
// ParamsKey is the request context key under which URL params are stored.
|
||||
//
|
||||
// This is only present from go 1.7.
|
||||
var ParamsKey = paramsKey{}
|
||||
|
||||
// Handler is an adapter which allows the usage of an http.Handler as a
|
||||
// request handle. With go 1.7+, the Params will be available in the
|
||||
// request context under ParamsKey.
|
||||
func (r *Router) Handler(method, path string, handler http.Handler) {
|
||||
r.Handle(method, path,
|
||||
func(w http.ResponseWriter, req *http.Request, p Params) {
|
||||
ctx := req.Context()
|
||||
ctx = context.WithValue(ctx, ParamsKey, p)
|
||||
req = req.WithContext(ctx)
|
||||
handler.ServeHTTP(w, req)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ParamsFromContext pulls the URL parameters from a request context,
|
||||
// or returns nil if none are present.
|
||||
//
|
||||
// This is only present from go 1.7.
|
||||
func ParamsFromContext(ctx context.Context) Params {
|
||||
p, _ := ctx.Value(ParamsKey).(Params)
|
||||
return p
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// +build !go1.7
|
||||
|
||||
package httprouter
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Handler is an adapter which allows the usage of an http.Handler as a
|
||||
// request handle. With go 1.7+, the Params will be available in the
|
||||
// request context under ParamsKey.
|
||||
func (r *Router) Handler(method, path string, handler http.Handler) {
|
||||
r.Handle(method, path,
|
||||
func(w http.ResponseWriter, req *http.Request, _ Params) {
|
||||
handler.ServeHTTP(w, req)
|
||||
},
|
||||
)
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<!--
|
||||
|
||||
For bug reports, please provide the version or the Git commit hash for
|
||||
which you have observed the behavior in question.
|
||||
|
||||
Note that we use GitHub issues for bugs and (uncontroversial) feature
|
||||
requests (see below for details).
|
||||
|
||||
Please do *NOT* ask usage questions in GitHub issues. Usage questions make
|
||||
most sense on the users mailing list, where more people are available to
|
||||
potentially respond to your question, and the whole community can benefit
|
||||
from the answers provided (perhaps your question has already been answered,
|
||||
search the archive to find out):
|
||||
https://groups.google.com/forum/#!forum/prometheus-users
|
||||
|
||||
While a GitHub issue is fine to track progress on an uncontroversial
|
||||
feature request, many feature requests touch the best practices and
|
||||
concepts of Prometheus as a whole and need to be discussed with the wider
|
||||
developer community first. This is in particular true for a request to
|
||||
reconsider a prior rejection of a feature request. Those overarching
|
||||
discussions happen on the developer mailing list (GitHub issues, in
|
||||
particular closed ones, are not tracked by the wider developer community
|
||||
and thus inadequate):
|
||||
https://groups.google.com/forum/#!forum/prometheus-developers
|
||||
|
||||
You can find more information at: https://prometheus.io/community/
|
||||
|
||||
-->
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
The Prometheus project was started by Matt T. Proud (emeritus) and
|
||||
Julius Volz in 2012.
|
||||
|
||||
Maintainers of this repository:
|
||||
|
||||
* Björn Rabenstein <beorn@soundcloud.com>
|
||||
|
||||
The following individuals have contributed code to this repository
|
||||
(listed in alphabetical order):
|
||||
|
||||
* Bernerd Schaefer <bj.schaefer@gmail.com>
|
||||
* Björn Rabenstein <beorn@soundcloud.com>
|
||||
* Daniel Bornkessel <daniel@soundcloud.com>
|
||||
* Jeff Younker <jeff@drinktomi.com>
|
||||
* Julius Volz <julius.volz@gmail.com>
|
||||
* Matt T. Proud <matt.proud@gmail.com>
|
||||
* Tobias Schmidt <ts@soundcloud.com>
|
||||
|
@ -0,0 +1,23 @@
|
||||
# This Dockerfile builds an image for a client_golang example.
|
||||
#
|
||||
# Use as (from the root for the client_golang repository):
|
||||
# docker build -f examples/$name/Dockerfile -t prometheus/golang-example-$name .
|
||||
|
||||
# Builder image, where we build the example.
|
||||
FROM golang:1 AS builder
|
||||
WORKDIR /go/src/github.com/prometheus/client_golang
|
||||
COPY . .
|
||||
WORKDIR /go/src/github.com/prometheus/client_golang/prometheus
|
||||
RUN go get -d
|
||||
WORKDIR /go/src/github.com/prometheus/client_golang/examples/random
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w'
|
||||
WORKDIR /go/src/github.com/prometheus/client_golang/examples/simple
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w'
|
||||
|
||||
# Final image.
|
||||
FROM prom/busybox
|
||||
LABEL maintainer="The Prometheus Authors <prometheus-developers@googlegroups.com>"
|
||||
COPY --from=builder /go/src/github.com/prometheus/client_golang/examples/random \
|
||||
/go/src/github.com/prometheus/client_golang/examples/simple ./
|
||||
EXPOSE 8080
|
||||
CMD echo Please run an example. Either /random or /simple
|
@ -0,0 +1,2 @@
|
||||
* Krasi Georgiev <kgeorgie@redhat.com> for `api/...`
|
||||
* Björn Rabenstein <beorn@soundcloud.com> for everything else
|
@ -0,0 +1,32 @@
|
||||
# Copyright 2018 The Prometheus Authors
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
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: get_dep
|
||||
get_dep:
|
||||
@echo ">> getting dependencies"
|
||||
$(GO) get -t ./...
|
||||
|
||||
.PHONY: test
|
||||
test: get_dep common-test
|
||||
|
||||
.PHONY: test-short
|
||||
test-short: get_dep common-test-short
|
@ -0,0 +1,132 @@
|
||||
# Copyright 2018 The Prometheus Authors
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# A common Makefile that includes rules to be reused in different prometheus projects.
|
||||
# !!! Open PRs only against the prometheus/prometheus/Makefile.common repository!
|
||||
|
||||
# Example usage :
|
||||
# Create the main Makefile in the root project directory.
|
||||
# include Makefile.common
|
||||
# customTarget:
|
||||
# @echo ">> Running customTarget"
|
||||
#
|
||||
|
||||
# Ensure GOBIN is not set during build so that promu is installed to the correct path
|
||||
unexport GOBIN
|
||||
|
||||
GO ?= go
|
||||
GOFMT ?= $(GO)fmt
|
||||
FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH)))
|
||||
PROMU := $(FIRST_GOPATH)/bin/promu
|
||||
STATICCHECK := $(FIRST_GOPATH)/bin/staticcheck
|
||||
GOVENDOR := $(FIRST_GOPATH)/bin/govendor
|
||||
pkgs = ./...
|
||||
|
||||
PREFIX ?= $(shell pwd)
|
||||
BIN_DIR ?= $(shell pwd)
|
||||
DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
|
||||
DOCKER_REPO ?= prom
|
||||
|
||||
.PHONY: all
|
||||
all: style staticcheck unused build test
|
||||
|
||||
# This rule is used to forward a target like "build" to "common-build". This
|
||||
# allows a new "build" target to be defined in a Makefile which includes this
|
||||
# one and override "common-build" without override warnings.
|
||||
%: common-% ;
|
||||
|
||||
.PHONY: common-style
|
||||
common-style:
|
||||
@echo ">> checking code style"
|
||||
@fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \
|
||||
if [ -n "$${fmtRes}" ]; then \
|
||||
echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \
|
||||
echo "Please ensure you are using $$($(GO) version) for formatting code."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: common-check_license
|
||||
common-check_license:
|
||||
@echo ">> checking license header"
|
||||
@licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \
|
||||
awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \
|
||||
done); \
|
||||
if [ -n "$${licRes}" ]; then \
|
||||
echo "license header checking failed:"; echo "$${licRes}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: common-test-short
|
||||
common-test-short:
|
||||
@echo ">> running short tests"
|
||||
$(GO) test -short $(pkgs)
|
||||
|
||||
.PHONY: common-test
|
||||
common-test:
|
||||
@echo ">> running all tests"
|
||||
$(GO) test -race $(pkgs)
|
||||
|
||||
.PHONY: common-format
|
||||
common-format:
|
||||
@echo ">> formatting code"
|
||||
$(GO) fmt $(pkgs)
|
||||
|
||||
.PHONY: common-vet
|
||||
common-vet:
|
||||
@echo ">> vetting code"
|
||||
$(GO) vet $(pkgs)
|
||||
|
||||
.PHONY: common-staticcheck
|
||||
common-staticcheck: $(STATICCHECK)
|
||||
@echo ">> running staticcheck"
|
||||
$(STATICCHECK) -ignore "$(STATICCHECK_IGNORE)" $(pkgs)
|
||||
|
||||
.PHONY: common-unused
|
||||
common-unused: $(GOVENDOR)
|
||||
@echo ">> running check for unused packages"
|
||||
@$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages'
|
||||
|
||||
.PHONY: common-build
|
||||
common-build: promu
|
||||
@echo ">> building binaries"
|
||||
$(PROMU) build --prefix $(PREFIX)
|
||||
|
||||
.PHONY: common-tarball
|
||||
common-tarball: promu
|
||||
@echo ">> building release tarball"
|
||||
$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR)
|
||||
|
||||
.PHONY: common-docker
|
||||
common-docker:
|
||||
docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" .
|
||||
|
||||
.PHONY: common-docker-publish
|
||||
common-docker-publish:
|
||||
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)"
|
||||
|
||||
.PHONY: common-docker-tag-latest
|
||||
common-docker-tag-latest:
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):latest"
|
||||
|
||||
.PHONY: promu
|
||||
promu:
|
||||
GOOS= GOARCH= $(GO) get -u github.com/prometheus/promu
|
||||
|
||||
.PHONY: $(STATICCHECK)
|
||||
$(STATICCHECK):
|
||||
GOOS= GOARCH= $(GO) get -u honnef.co/go/tools/cmd/staticcheck
|
||||
|
||||
.PHONY: $(GOVENDOR)
|
||||
$(GOVENDOR):
|
||||
GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor
|
@ -1 +1 @@
|
||||
0.8.0
|
||||
0.9.0
|
||||
|
@ -0,0 +1,131 @@
|
||||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
// Package api provides clients for the HTTP APIs.
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DefaultRoundTripper is used if no RoundTripper is set in Config.
|
||||
var DefaultRoundTripper http.RoundTripper = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// Config defines configuration parameters for a new client.
|
||||
type Config struct {
|
||||
// The address of the Prometheus to connect to.
|
||||
Address string
|
||||
|
||||
// RoundTripper is used by the Client to drive HTTP requests. If not
|
||||
// provided, DefaultRoundTripper will be used.
|
||||
RoundTripper http.RoundTripper
|
||||
}
|
||||
|
||||
func (cfg *Config) roundTripper() http.RoundTripper {
|
||||
if cfg.RoundTripper == nil {
|
||||
return DefaultRoundTripper
|
||||
}
|
||||
return cfg.RoundTripper
|
||||
}
|
||||
|
||||
// Client is the interface for an API client.
|
||||
type Client interface {
|
||||
URL(ep string, args map[string]string) *url.URL
|
||||
Do(context.Context, *http.Request) (*http.Response, []byte, error)
|
||||
}
|
||||
|
||||
// NewClient returns a new Client.
|
||||
//
|
||||
// It is safe to use the returned Client from multiple goroutines.
|
||||
func NewClient(cfg Config) (Client, error) {
|
||||
u, err := url.Parse(cfg.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Path = strings.TrimRight(u.Path, "/")
|
||||
|
||||
return &httpClient{
|
||||
endpoint: u,
|
||||
client: http.Client{Transport: cfg.roundTripper()},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type httpClient struct {
|
||||
endpoint *url.URL
|
||||
client http.Client
|
||||
}
|
||||
|
||||
func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
|
||||
p := path.Join(c.endpoint.Path, ep)
|
||||
|
||||
for arg, val := range args {
|
||||
arg = ":" + arg
|
||||
p = strings.Replace(p, arg, val, -1)
|
||||
}
|
||||
|
||||
u := *c.endpoint
|
||||
u.Path = p
|
||||
|
||||
return &u
|
||||
}
|
||||
|
||||
func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
if ctx != nil {
|
||||
req = req.WithContext(ctx)
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
defer func() {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var body []byte
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = resp.Body.Close()
|
||||
<-done
|
||||
if err == nil {
|
||||
err = ctx.Err()
|
||||
}
|
||||
case <-done:
|
||||
}
|
||||
|
||||
return resp, body, err
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
c := Config{}
|
||||
if c.roundTripper() != DefaultRoundTripper {
|
||||
t.Fatalf("expected default roundtripper for nil RoundTripper field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
address string
|
||||
endpoint string
|
||||
args map[string]string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "/test",
|
||||
expected: "http://localhost:9090/test",
|
||||
},
|
||||
{
|
||||
address: "http://localhost",
|
||||
endpoint: "/test",
|
||||
expected: "http://localhost/test",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "test",
|
||||
expected: "http://localhost:9090/test",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090/prefix",
|
||||
endpoint: "/test",
|
||||
expected: "http://localhost:9090/prefix/test",
|
||||
},
|
||||
{
|
||||
address: "https://localhost:9090/",
|
||||
endpoint: "/test/",
|
||||
expected: "https://localhost:9090/test",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "/test/:param",
|
||||
args: map[string]string{
|
||||
"param": "content",
|
||||
},
|
||||
expected: "http://localhost:9090/test/content",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "/test/:param/more/:param",
|
||||
args: map[string]string{
|
||||
"param": "content",
|
||||
},
|
||||
expected: "http://localhost:9090/test/content/more/content",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "/test/:param/more/:foo",
|
||||
args: map[string]string{
|
||||
"param": "content",
|
||||
"foo": "bar",
|
||||
},
|
||||
expected: "http://localhost:9090/test/content/more/bar",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "/test/:param",
|
||||
args: map[string]string{
|
||||
"nonexistent": "content",
|
||||
},
|
||||
expected: "http://localhost:9090/test/:param",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ep, err := url.Parse(test.address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hclient := &httpClient{
|
||||
endpoint: ep,
|
||||
client: http.Client{Transport: DefaultRoundTripper},
|
||||
}
|
||||
|
||||
u := hclient.URL(test.endpoint, test.args)
|
||||
if u.String() != test.expected {
|
||||
t.Errorf("unexpected result: got %s, want %s", u, test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
@ -1,345 +0,0 @@
|
||||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package prometheus provides bindings to the Prometheus HTTP API:
|
||||
// http://prometheus.io/docs/querying/api/
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/context/ctxhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
statusAPIError = 422
|
||||
apiPrefix = "/api/v1"
|
||||
|
||||
epQuery = "/query"
|
||||
epQueryRange = "/query_range"
|
||||
epLabelValues = "/label/:name/values"
|
||||
epSeries = "/series"
|
||||
)
|
||||
|
||||
type ErrorType string
|
||||
|
||||
const (
|
||||
// The different API error types.
|
||||
ErrBadData ErrorType = "bad_data"
|
||||
ErrTimeout = "timeout"
|
||||
ErrCanceled = "canceled"
|
||||
ErrExec = "execution"
|
||||
ErrBadResponse = "bad_response"
|
||||
)
|
||||
|
||||
// Error is an error returned by the API.
|
||||
type Error struct {
|
||||
Type ErrorType
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Type, e.Msg)
|
||||
}
|
||||
|
||||
// CancelableTransport is like net.Transport but provides
|
||||
// per-request cancelation functionality.
|
||||
type CancelableTransport interface {
|
||||
http.RoundTripper
|
||||
CancelRequest(req *http.Request)
|
||||
}
|
||||
|
||||
var DefaultTransport CancelableTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// Config defines configuration parameters for a new client.
|
||||
type Config struct {
|
||||
// The address of the Prometheus to connect to.
|
||||
Address string
|
||||
|
||||
// Transport is used by the Client to drive HTTP requests. If not
|
||||
// provided, DefaultTransport will be used.
|
||||
Transport CancelableTransport
|
||||
}
|
||||
|
||||
func (cfg *Config) transport() CancelableTransport {
|
||||
if cfg.Transport == nil {
|
||||
return DefaultTransport
|
||||
}
|
||||
return cfg.Transport
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
url(ep string, args map[string]string) *url.URL
|
||||
do(context.Context, *http.Request) (*http.Response, []byte, error)
|
||||
}
|
||||
|
||||
// New returns a new Client.
|
||||
//
|
||||
// It is safe to use the returned Client from multiple goroutines.
|
||||
func New(cfg Config) (Client, error) {
|
||||
u, err := url.Parse(cfg.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Path = strings.TrimRight(u.Path, "/") + apiPrefix
|
||||
|
||||
return &httpClient{
|
||||
endpoint: u,
|
||||
transport: cfg.transport(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type httpClient struct {
|
||||
endpoint *url.URL
|
||||
transport CancelableTransport
|
||||
}
|
||||
|
||||
func (c *httpClient) url(ep string, args map[string]string) *url.URL {
|
||||
p := path.Join(c.endpoint.Path, ep)
|
||||
|
||||
for arg, val := range args {
|
||||
arg = ":" + arg
|
||||
p = strings.Replace(p, arg, val, -1)
|
||||
}
|
||||
|
||||
u := *c.endpoint
|
||||
u.Path = p
|
||||
|
||||
return &u
|
||||
}
|
||||
|
||||
func (c *httpClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
resp, err := ctxhttp.Do(ctx, &http.Client{Transport: c.transport}, req)
|
||||
|
||||
defer func() {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var body []byte
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = resp.Body.Close()
|
||||
<-done
|
||||
if err == nil {
|
||||
err = ctx.Err()
|
||||
}
|
||||
case <-done:
|
||||
}
|
||||
|
||||
return resp, body, err
|
||||
}
|
||||
|
||||
// apiClient wraps a regular client and processes successful API responses.
|
||||
// Successful also includes responses that errored at the API level.
|
||||
type apiClient struct {
|
||||
Client
|
||||
}
|
||||
|
||||
type apiResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
ErrorType ErrorType `json:"errorType"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func (c apiClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
resp, body, err := c.Client.do(ctx, req)
|
||||
if err != nil {
|
||||
return resp, body, err
|
||||
}
|
||||
|
||||
code := resp.StatusCode
|
||||
|
||||
if code/100 != 2 && code != statusAPIError {
|
||||
return resp, body, &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: fmt.Sprintf("bad response code %d", resp.StatusCode),
|
||||
}
|
||||
}
|
||||
|
||||
var result apiResponse
|
||||
|
||||
if err = json.Unmarshal(body, &result); err != nil {
|
||||
return resp, body, &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
if (code == statusAPIError) != (result.Status == "error") {
|
||||
err = &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "inconsistent body for response code",
|
||||
}
|
||||
}
|
||||
|
||||
if code == statusAPIError && result.Status == "error" {
|
||||
err = &Error{
|
||||
Type: result.ErrorType,
|
||||
Msg: result.Error,
|
||||
}
|
||||
}
|
||||
|
||||
return resp, []byte(result.Data), err
|
||||
}
|
||||
|
||||
// Range represents a sliced time range.
|
||||
type Range struct {
|
||||
// The boundaries of the time range.
|
||||
Start, End time.Time
|
||||
// The maximum time between two slices within the boundaries.
|
||||
Step time.Duration
|
||||
}
|
||||
|
||||
// queryResult contains result data for a query.
|
||||
type queryResult struct {
|
||||
Type model.ValueType `json:"resultType"`
|
||||
Result interface{} `json:"result"`
|
||||
|
||||
// The decoded value.
|
||||
v model.Value
|
||||
}
|
||||
|
||||
func (qr *queryResult) UnmarshalJSON(b []byte) error {
|
||||
v := struct {
|
||||
Type model.ValueType `json:"resultType"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
}{}
|
||||
|
||||
err := json.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v.Type {
|
||||
case model.ValScalar:
|
||||
var sv model.Scalar
|
||||
err = json.Unmarshal(v.Result, &sv)
|
||||
qr.v = &sv
|
||||
|
||||
case model.ValVector:
|
||||
var vv model.Vector
|
||||
err = json.Unmarshal(v.Result, &vv)
|
||||
qr.v = vv
|
||||
|
||||
case model.ValMatrix:
|
||||
var mv model.Matrix
|
||||
err = json.Unmarshal(v.Result, &mv)
|
||||
qr.v = mv
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("unexpected value type %q", v.Type)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// QueryAPI provides bindings the Prometheus's query API.
|
||||
type QueryAPI interface {
|
||||
// Query performs a query for the given time.
|
||||
Query(ctx context.Context, query string, ts time.Time) (model.Value, error)
|
||||
// Query performs a query for the given range.
|
||||
QueryRange(ctx context.Context, query string, r Range) (model.Value, error)
|
||||
}
|
||||
|
||||
// NewQueryAPI returns a new QueryAPI for the client.
|
||||
//
|
||||
// It is safe to use the returned QueryAPI from multiple goroutines.
|
||||
func NewQueryAPI(c Client) QueryAPI {
|
||||
return &httpQueryAPI{client: apiClient{c}}
|
||||
}
|
||||
|
||||
type httpQueryAPI struct {
|
||||
client Client
|
||||
}
|
||||
|
||||
func (h *httpQueryAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) {
|
||||
u := h.client.url(epQuery, nil)
|
||||
q := u.Query()
|
||||
|
||||
q.Set("query", query)
|
||||
q.Set("time", ts.Format(time.RFC3339Nano))
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
|
||||
_, body, err := h.client.do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var qres queryResult
|
||||
err = json.Unmarshal(body, &qres)
|
||||
|
||||
return model.Value(qres.v), err
|
||||
}
|
||||
|
||||
func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) {
|
||||
u := h.client.url(epQueryRange, nil)
|
||||
q := u.Query()
|
||||
|
||||
var (
|
||||
start = r.Start.Format(time.RFC3339Nano)
|
||||
end = r.End.Format(time.RFC3339Nano)
|
||||
step = strconv.FormatFloat(r.Step.Seconds(), 'f', 3, 64)
|
||||
)
|
||||
|
||||
q.Set("query", query)
|
||||
q.Set("start", start)
|
||||
q.Set("end", end)
|
||||
q.Set("step", step)
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
|
||||
_, body, err := h.client.do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var qres queryResult
|
||||
err = json.Unmarshal(body, &qres)
|
||||
|
||||
return model.Value(qres.v), err
|
||||
}
|
@ -1,453 +0,0 @@
|
||||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
c := Config{}
|
||||
if c.transport() != DefaultTransport {
|
||||
t.Fatalf("expected default transport for nil Transport field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
address string
|
||||
endpoint string
|
||||
args map[string]string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "/test",
|
||||
expected: "http://localhost:9090/test",
|
||||
},
|
||||
{
|
||||
address: "http://localhost",
|
||||
endpoint: "/test",
|
||||
expected: "http://localhost/test",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "test",
|
||||
expected: "http://localhost:9090/test",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090/prefix",
|
||||
endpoint: "/test",
|
||||
expected: "http://localhost:9090/prefix/test",
|
||||
},
|
||||
{
|
||||
address: "https://localhost:9090/",
|
||||
endpoint: "/test/",
|
||||
expected: "https://localhost:9090/test",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "/test/:param",
|
||||
args: map[string]string{
|
||||
"param": "content",
|
||||
},
|
||||
expected: "http://localhost:9090/test/content",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "/test/:param/more/:param",
|
||||
args: map[string]string{
|
||||
"param": "content",
|
||||
},
|
||||
expected: "http://localhost:9090/test/content/more/content",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "/test/:param/more/:foo",
|
||||
args: map[string]string{
|
||||
"param": "content",
|
||||
"foo": "bar",
|
||||
},
|
||||
expected: "http://localhost:9090/test/content/more/bar",
|
||||
},
|
||||
{
|
||||
address: "http://localhost:9090",
|
||||
endpoint: "/test/:param",
|
||||
args: map[string]string{
|
||||
"nonexistant": "content",
|
||||
},
|
||||
expected: "http://localhost:9090/test/:param",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ep, err := url.Parse(test.address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hclient := &httpClient{
|
||||
endpoint: ep,
|
||||
transport: DefaultTransport,
|
||||
}
|
||||
|
||||
u := hclient.url(test.endpoint, test.args)
|
||||
if u.String() != test.expected {
|
||||
t.Errorf("unexpected result: got %s, want %s", u, test.expected)
|
||||
continue
|
||||
}
|
||||
|
||||
// The apiClient must return exactly the same result as the httpClient.
|
||||
aclient := &apiClient{hclient}
|
||||
|
||||
u = aclient.url(test.endpoint, test.args)
|
||||
if u.String() != test.expected {
|
||||
t.Errorf("unexpected result: got %s, want %s", u, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testClient struct {
|
||||
*testing.T
|
||||
|
||||
ch chan apiClientTest
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
type apiClientTest struct {
|
||||
code int
|
||||
response interface{}
|
||||
expected string
|
||||
err *Error
|
||||
}
|
||||
|
||||
func (c *testClient) url(ep string, args map[string]string) *url.URL {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *testClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
if ctx == nil {
|
||||
c.Fatalf("context was not passed down")
|
||||
}
|
||||
if req != c.req {
|
||||
c.Fatalf("request was not passed down")
|
||||
}
|
||||
|
||||
test := <-c.ch
|
||||
|
||||
var b []byte
|
||||
var err error
|
||||
|
||||
switch v := test.response.(type) {
|
||||
case string:
|
||||
b = []byte(v)
|
||||
default:
|
||||
b, err = json.Marshal(v)
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
resp := &http.Response{
|
||||
StatusCode: test.code,
|
||||
}
|
||||
|
||||
return resp, b, nil
|
||||
}
|
||||
|
||||
func TestAPIClientDo(t *testing.T) {
|
||||
tests := []apiClientTest{
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "error",
|
||||
Data: json.RawMessage(`null`),
|
||||
ErrorType: ErrBadData,
|
||||
Error: "failed",
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrBadData,
|
||||
Msg: "failed",
|
||||
},
|
||||
code: statusAPIError,
|
||||
expected: `null`,
|
||||
},
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "error",
|
||||
Data: json.RawMessage(`"test"`),
|
||||
ErrorType: ErrTimeout,
|
||||
Error: "timed out",
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrTimeout,
|
||||
Msg: "timed out",
|
||||
},
|
||||
code: statusAPIError,
|
||||
expected: `test`,
|
||||
},
|
||||
{
|
||||
response: "bad json",
|
||||
err: &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "bad response code 400",
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
response: "bad json",
|
||||
err: &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "invalid character 'b' looking for beginning of value",
|
||||
},
|
||||
code: statusAPIError,
|
||||
},
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "success",
|
||||
Data: json.RawMessage(`"test"`),
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "inconsistent body for response code",
|
||||
},
|
||||
code: statusAPIError,
|
||||
},
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "success",
|
||||
Data: json.RawMessage(`"test"`),
|
||||
ErrorType: ErrTimeout,
|
||||
Error: "timed out",
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "inconsistent body for response code",
|
||||
},
|
||||
code: statusAPIError,
|
||||
},
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "error",
|
||||
Data: json.RawMessage(`"test"`),
|
||||
ErrorType: ErrTimeout,
|
||||
Error: "timed out",
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "inconsistent body for response code",
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
tc := &testClient{
|
||||
T: t,
|
||||
ch: make(chan apiClientTest, 1),
|
||||
req: &http.Request{},
|
||||
}
|
||||
client := &apiClient{tc}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
tc.ch <- test
|
||||
|
||||
_, body, err := client.do(context.Background(), tc.req)
|
||||
|
||||
if test.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("expected error %q but got none", test.err)
|
||||
continue
|
||||
}
|
||||
if test.err.Error() != err.Error() {
|
||||
t.Errorf("unexpected error: want %q, got %q", test.err, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpeceted error %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
want, got := test.expected, string(body)
|
||||
if want != got {
|
||||
t.Errorf("unexpected body: want %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type apiTestClient struct {
|
||||
*testing.T
|
||||
curTest apiTest
|
||||
}
|
||||
|
||||
type apiTest struct {
|
||||
do func() (interface{}, error)
|
||||
inErr error
|
||||
inRes interface{}
|
||||
|
||||
reqPath string
|
||||
reqParam url.Values
|
||||
reqMethod string
|
||||
res interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *apiTestClient) url(ep string, args map[string]string) *url.URL {
|
||||
u := &url.URL{
|
||||
Host: "test:9090",
|
||||
Path: apiPrefix + ep,
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (c *apiTestClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
|
||||
test := c.curTest
|
||||
|
||||
if req.URL.Path != test.reqPath {
|
||||
c.Errorf("unexpected request path: want %s, got %s", test.reqPath, req.URL.Path)
|
||||
}
|
||||
if req.Method != test.reqMethod {
|
||||
c.Errorf("unexpected request method: want %s, got %s", test.reqMethod, req.Method)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(test.inRes)
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
|
||||
resp := &http.Response{}
|
||||
if test.inErr != nil {
|
||||
resp.StatusCode = statusAPIError
|
||||
} else {
|
||||
resp.StatusCode = http.StatusOK
|
||||
}
|
||||
|
||||
return resp, b, test.inErr
|
||||
}
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
|
||||
testTime := time.Now()
|
||||
|
||||
client := &apiTestClient{T: t}
|
||||
|
||||
queryApi := &httpQueryAPI{
|
||||
client: client,
|
||||
}
|
||||
|
||||
doQuery := func(q string, ts time.Time) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return queryApi.Query(context.Background(), q, ts)
|
||||
}
|
||||
}
|
||||
|
||||
doQueryRange := func(q string, rng Range) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return queryApi.QueryRange(context.Background(), q, rng)
|
||||
}
|
||||
}
|
||||
|
||||
queryTests := []apiTest{
|
||||
{
|
||||
do: doQuery("2", testTime),
|
||||
inRes: &queryResult{
|
||||
Type: model.ValScalar,
|
||||
Result: &model.Scalar{
|
||||
Value: 2,
|
||||
Timestamp: model.TimeFromUnix(testTime.Unix()),
|
||||
},
|
||||
},
|
||||
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/query",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
res: &model.Scalar{
|
||||
Value: 2,
|
||||
Timestamp: model.TimeFromUnix(testTime.Unix()),
|
||||
},
|
||||
},
|
||||
{
|
||||
do: doQuery("2", testTime),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/query",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doQueryRange("2", Range{
|
||||
Start: testTime.Add(-time.Minute),
|
||||
End: testTime,
|
||||
Step: time.Minute,
|
||||
}),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/query_range",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
||||
"step": []string{time.Minute.String()},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
}
|
||||
|
||||
var tests []apiTest
|
||||
tests = append(tests, queryTests...)
|
||||
|
||||
for _, test := range tests {
|
||||
client.curTest = test
|
||||
|
||||
res, err := test.do()
|
||||
|
||||
if test.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("expected error %q but got none", test.err)
|
||||
continue
|
||||
}
|
||||
if err.Error() != test.err.Error() {
|
||||
t.Errorf("unexpected error: want %s, got %s", test.err, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(res, test.res) {
|
||||
t.Errorf("unexpected result: want %v, got %v", test.res, res)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,504 @@
|
||||
// Copyright 2017 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.7
|
||||
|
||||
// Package v1 provides bindings to the Prometheus HTTP API v1:
|
||||
// http://prometheus.io/docs/querying/api/
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/api"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
const (
|
||||
statusAPIError = 422
|
||||
|
||||
apiPrefix = "/api/v1"
|
||||
|
||||
epAlertManagers = apiPrefix + "/alertmanagers"
|
||||
epQuery = apiPrefix + "/query"
|
||||
epQueryRange = apiPrefix + "/query_range"
|
||||
epLabelValues = apiPrefix + "/label/:name/values"
|
||||
epSeries = apiPrefix + "/series"
|
||||
epTargets = apiPrefix + "/targets"
|
||||
epSnapshot = apiPrefix + "/admin/tsdb/snapshot"
|
||||
epDeleteSeries = apiPrefix + "/admin/tsdb/delete_series"
|
||||
epCleanTombstones = apiPrefix + "/admin/tsdb/clean_tombstones"
|
||||
epConfig = apiPrefix + "/status/config"
|
||||
epFlags = apiPrefix + "/status/flags"
|
||||
)
|
||||
|
||||
// ErrorType models the different API error types.
|
||||
type ErrorType string
|
||||
|
||||
// HealthStatus models the health status of a scrape target.
|
||||
type HealthStatus string
|
||||
|
||||
const (
|
||||
// Possible values for ErrorType.
|
||||
ErrBadData ErrorType = "bad_data"
|
||||
ErrTimeout = "timeout"
|
||||
ErrCanceled = "canceled"
|
||||
ErrExec = "execution"
|
||||
ErrBadResponse = "bad_response"
|
||||
|
||||
// Possible values for HealthStatus.
|
||||
HealthGood HealthStatus = "up"
|
||||
HealthUnknown HealthStatus = "unknown"
|
||||
HealthBad HealthStatus = "down"
|
||||
)
|
||||
|
||||
// Error is an error returned by the API.
|
||||
type Error struct {
|
||||
Type ErrorType
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Type, e.Msg)
|
||||
}
|
||||
|
||||
// Range represents a sliced time range.
|
||||
type Range struct {
|
||||
// The boundaries of the time range.
|
||||
Start, End time.Time
|
||||
// The maximum time between two slices within the boundaries.
|
||||
Step time.Duration
|
||||
}
|
||||
|
||||
// API provides bindings for Prometheus's v1 API.
|
||||
type API interface {
|
||||
// AlertManagers returns an overview of the current state of the Prometheus alert manager discovery.
|
||||
AlertManagers(ctx context.Context) (AlertManagersResult, error)
|
||||
// CleanTombstones removes the deleted data from disk and cleans up the existing tombstones.
|
||||
CleanTombstones(ctx context.Context) error
|
||||
// Config returns the current Prometheus configuration.
|
||||
Config(ctx context.Context) (ConfigResult, error)
|
||||
// DeleteSeries deletes data for a selection of series in a time range.
|
||||
DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error
|
||||
// Flags returns the flag values that Prometheus was launched with.
|
||||
Flags(ctx context.Context) (FlagsResult, error)
|
||||
// LabelValues performs a query for the values of the given label.
|
||||
LabelValues(ctx context.Context, label string) (model.LabelValues, error)
|
||||
// Query performs a query for the given time.
|
||||
Query(ctx context.Context, query string, ts time.Time) (model.Value, error)
|
||||
// QueryRange performs a query for the given range.
|
||||
QueryRange(ctx context.Context, query string, r Range) (model.Value, error)
|
||||
// Series finds series by label matchers.
|
||||
Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error)
|
||||
// Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand>
|
||||
// under the TSDB's data directory and returns the directory as response.
|
||||
Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error)
|
||||
// Targets returns an overview of the current state of the Prometheus target discovery.
|
||||
Targets(ctx context.Context) (TargetsResult, error)
|
||||
}
|
||||
|
||||
// AlertManagersResult contains the result from querying the alertmanagers endpoint.
|
||||
type AlertManagersResult struct {
|
||||
Active []AlertManager `json:"activeAlertManagers"`
|
||||
Dropped []AlertManager `json:"droppedAlertManagers"`
|
||||
}
|
||||
|
||||
// AlertManager models a configured Alert Manager.
|
||||
type AlertManager struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// ConfigResult contains the result from querying the config endpoint.
|
||||
type ConfigResult struct {
|
||||
YAML string `json:"yaml"`
|
||||
}
|
||||
|
||||
// FlagsResult contains the result from querying the flag endpoint.
|
||||
type FlagsResult map[string]string
|
||||
|
||||
// SnapshotResult contains the result from querying the snapshot endpoint.
|
||||
type SnapshotResult struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// TargetsResult contains the result from querying the targets endpoint.
|
||||
type TargetsResult struct {
|
||||
Active []ActiveTarget `json:"activeTargets"`
|
||||
Dropped []DroppedTarget `json:"droppedTargets"`
|
||||
}
|
||||
|
||||
// ActiveTarget models an active Prometheus scrape target.
|
||||
type ActiveTarget struct {
|
||||
DiscoveredLabels model.LabelSet `json:"discoveredLabels"`
|
||||
Labels model.LabelSet `json:"labels"`
|
||||
ScrapeURL string `json:"scrapeUrl"`
|
||||
LastError string `json:"lastError"`
|
||||
LastScrape time.Time `json:"lastScrape"`
|
||||
Health HealthStatus `json:"health"`
|
||||
}
|
||||
|
||||
// DroppedTarget models a dropped Prometheus scrape target.
|
||||
type DroppedTarget struct {
|
||||
DiscoveredLabels model.LabelSet `json:"discoveredLabels"`
|
||||
}
|
||||
|
||||
// queryResult contains result data for a query.
|
||||
type queryResult struct {
|
||||
Type model.ValueType `json:"resultType"`
|
||||
Result interface{} `json:"result"`
|
||||
|
||||
// The decoded value.
|
||||
v model.Value
|
||||
}
|
||||
|
||||
func (qr *queryResult) UnmarshalJSON(b []byte) error {
|
||||
v := struct {
|
||||
Type model.ValueType `json:"resultType"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
}{}
|
||||
|
||||
err := json.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v.Type {
|
||||
case model.ValScalar:
|
||||
var sv model.Scalar
|
||||
err = json.Unmarshal(v.Result, &sv)
|
||||
qr.v = &sv
|
||||
|
||||
case model.ValVector:
|
||||
var vv model.Vector
|
||||
err = json.Unmarshal(v.Result, &vv)
|
||||
qr.v = vv
|
||||
|
||||
case model.ValMatrix:
|
||||
var mv model.Matrix
|
||||
err = json.Unmarshal(v.Result, &mv)
|
||||
qr.v = mv
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("unexpected value type %q", v.Type)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewAPI returns a new API for the client.
|
||||
//
|
||||
// It is safe to use the returned API from multiple goroutines.
|
||||
func NewAPI(c api.Client) API {
|
||||
return &httpAPI{client: apiClient{c}}
|
||||
}
|
||||
|
||||
type httpAPI struct {
|
||||
client api.Client
|
||||
}
|
||||
|
||||
func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, error) {
|
||||
u := h.client.URL(epAlertManagers, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return AlertManagersResult{}, err
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return AlertManagersResult{}, err
|
||||
}
|
||||
|
||||
var res AlertManagersResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (h *httpAPI) CleanTombstones(ctx context.Context) error {
|
||||
u := h.client.URL(epCleanTombstones, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, u.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = h.client.Do(ctx, req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) {
|
||||
u := h.client.URL(epConfig, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return ConfigResult{}, err
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return ConfigResult{}, err
|
||||
}
|
||||
|
||||
var res ConfigResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error {
|
||||
u := h.client.URL(epDeleteSeries, nil)
|
||||
q := u.Query()
|
||||
|
||||
for _, m := range matches {
|
||||
q.Add("match[]", m)
|
||||
}
|
||||
|
||||
q.Set("start", startTime.Format(time.RFC3339Nano))
|
||||
q.Set("end", endTime.Format(time.RFC3339Nano))
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, u.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = h.client.Do(ctx, req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) {
|
||||
u := h.client.URL(epFlags, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return FlagsResult{}, err
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return FlagsResult{}, err
|
||||
}
|
||||
|
||||
var res FlagsResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) {
|
||||
u := h.client.URL(epLabelValues, map[string]string{"name": label})
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var labelValues model.LabelValues
|
||||
err = json.Unmarshal(body, &labelValues)
|
||||
return labelValues, err
|
||||
}
|
||||
|
||||
func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) {
|
||||
u := h.client.URL(epQuery, nil)
|
||||
q := u.Query()
|
||||
|
||||
q.Set("query", query)
|
||||
if !ts.IsZero() {
|
||||
q.Set("time", ts.Format(time.RFC3339Nano))
|
||||
}
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var qres queryResult
|
||||
err = json.Unmarshal(body, &qres)
|
||||
|
||||
return model.Value(qres.v), err
|
||||
}
|
||||
|
||||
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) {
|
||||
u := h.client.URL(epQueryRange, nil)
|
||||
q := u.Query()
|
||||
|
||||
var (
|
||||
start = r.Start.Format(time.RFC3339Nano)
|
||||
end = r.End.Format(time.RFC3339Nano)
|
||||
step = strconv.FormatFloat(r.Step.Seconds(), 'f', 3, 64)
|
||||
)
|
||||
|
||||
q.Set("query", query)
|
||||
q.Set("start", start)
|
||||
q.Set("end", end)
|
||||
q.Set("step", step)
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var qres queryResult
|
||||
err = json.Unmarshal(body, &qres)
|
||||
|
||||
return model.Value(qres.v), err
|
||||
}
|
||||
|
||||
func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error) {
|
||||
u := h.client.URL(epSeries, nil)
|
||||
q := u.Query()
|
||||
|
||||
for _, m := range matches {
|
||||
q.Add("match[]", m)
|
||||
}
|
||||
|
||||
q.Set("start", startTime.Format(time.RFC3339Nano))
|
||||
q.Set("end", endTime.Format(time.RFC3339Nano))
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mset []model.LabelSet
|
||||
err = json.Unmarshal(body, &mset)
|
||||
return mset, err
|
||||
}
|
||||
|
||||
func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) {
|
||||
u := h.client.URL(epSnapshot, nil)
|
||||
q := u.Query()
|
||||
|
||||
q.Set("skip_head", strconv.FormatBool(skipHead))
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, u.String(), nil)
|
||||
if err != nil {
|
||||
return SnapshotResult{}, err
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return SnapshotResult{}, err
|
||||
}
|
||||
|
||||
var res SnapshotResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) {
|
||||
u := h.client.URL(epTargets, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return TargetsResult{}, err
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return TargetsResult{}, err
|
||||
}
|
||||
|
||||
var res TargetsResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// apiClient wraps a regular client and processes successful API responses.
|
||||
// Successful also includes responses that errored at the API level.
|
||||
type apiClient struct {
|
||||
api.Client
|
||||
}
|
||||
|
||||
type apiResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
ErrorType ErrorType `json:"errorType"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func apiError(code int) bool {
|
||||
// These are the codes that Prometheus sends when it returns an error.
|
||||
return code == statusAPIError || code == http.StatusBadRequest
|
||||
}
|
||||
|
||||
func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
resp, body, err := c.Client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return resp, body, err
|
||||
}
|
||||
|
||||
code := resp.StatusCode
|
||||
|
||||
if code/100 != 2 && !apiError(code) {
|
||||
return resp, body, &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: fmt.Sprintf("bad response code %d", resp.StatusCode),
|
||||
}
|
||||
}
|
||||
|
||||
var result apiResponse
|
||||
|
||||
if http.StatusNoContent != code {
|
||||
if err = json.Unmarshal(body, &result); err != nil {
|
||||
return resp, body, &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: err.Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if apiError(code) != (result.Status == "error") {
|
||||
err = &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "inconsistent body for response code",
|
||||
}
|
||||
}
|
||||
|
||||
if apiError(code) && result.Status == "error" {
|
||||
err = &Error{
|
||||
Type: result.ErrorType,
|
||||
Msg: result.Error,
|
||||
}
|
||||
}
|
||||
|
||||
return resp, []byte(result.Data), err
|
||||
}
|
@ -0,0 +1,706 @@
|
||||
// Copyright 2017 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.7
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
type apiTest struct {
|
||||
do func() (interface{}, error)
|
||||
inErr error
|
||||
inRes interface{}
|
||||
|
||||
reqPath string
|
||||
reqParam url.Values
|
||||
reqMethod string
|
||||
res interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
type apiTestClient struct {
|
||||
*testing.T
|
||||
curTest apiTest
|
||||
}
|
||||
|
||||
func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL {
|
||||
path := ep
|
||||
for k, v := range args {
|
||||
path = strings.Replace(path, ":"+k, v, -1)
|
||||
}
|
||||
u := &url.URL{
|
||||
Host: "test:9090",
|
||||
Path: path,
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
|
||||
test := c.curTest
|
||||
|
||||
if req.URL.Path != test.reqPath {
|
||||
c.Errorf("unexpected request path: want %s, got %s", test.reqPath, req.URL.Path)
|
||||
}
|
||||
if req.Method != test.reqMethod {
|
||||
c.Errorf("unexpected request method: want %s, got %s", test.reqMethod, req.Method)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(test.inRes)
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
|
||||
resp := &http.Response{}
|
||||
if test.inErr != nil {
|
||||
resp.StatusCode = statusAPIError
|
||||
} else {
|
||||
resp.StatusCode = http.StatusOK
|
||||
}
|
||||
|
||||
return resp, b, test.inErr
|
||||
}
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
|
||||
testTime := time.Now()
|
||||
|
||||
client := &apiTestClient{T: t}
|
||||
|
||||
promAPI := &httpAPI{
|
||||
client: client,
|
||||
}
|
||||
|
||||
doAlertManagers := func() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return promAPI.AlertManagers(context.Background())
|
||||
}
|
||||
}
|
||||
|
||||
doCleanTombstones := func() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return nil, promAPI.CleanTombstones(context.Background())
|
||||
}
|
||||
}
|
||||
|
||||
doConfig := func() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return promAPI.Config(context.Background())
|
||||
}
|
||||
}
|
||||
|
||||
doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime)
|
||||
}
|
||||
}
|
||||
|
||||
doFlags := func() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return promAPI.Flags(context.Background())
|
||||
}
|
||||
}
|
||||
|
||||
doLabelValues := func(label string) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return promAPI.LabelValues(context.Background(), label)
|
||||
}
|
||||
}
|
||||
|
||||
doQuery := func(q string, ts time.Time) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return promAPI.Query(context.Background(), q, ts)
|
||||
}
|
||||
}
|
||||
|
||||
doQueryRange := func(q string, rng Range) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return promAPI.QueryRange(context.Background(), q, rng)
|
||||
}
|
||||
}
|
||||
|
||||
doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return promAPI.Series(context.Background(), []string{matcher}, startTime, endTime)
|
||||
}
|
||||
}
|
||||
|
||||
doSnapshot := func(skipHead bool) func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return promAPI.Snapshot(context.Background(), skipHead)
|
||||
}
|
||||
}
|
||||
|
||||
doTargets := func() func() (interface{}, error) {
|
||||
return func() (interface{}, error) {
|
||||
return promAPI.Targets(context.Background())
|
||||
}
|
||||
}
|
||||
|
||||
queryTests := []apiTest{
|
||||
{
|
||||
do: doQuery("2", testTime),
|
||||
inRes: &queryResult{
|
||||
Type: model.ValScalar,
|
||||
Result: &model.Scalar{
|
||||
Value: 2,
|
||||
Timestamp: model.TimeFromUnix(testTime.Unix()),
|
||||
},
|
||||
},
|
||||
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/query",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
res: &model.Scalar{
|
||||
Value: 2,
|
||||
Timestamp: model.TimeFromUnix(testTime.Unix()),
|
||||
},
|
||||
},
|
||||
{
|
||||
do: doQuery("2", testTime),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/query",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doQueryRange("2", Range{
|
||||
Start: testTime.Add(-time.Minute),
|
||||
End: testTime,
|
||||
Step: time.Minute,
|
||||
}),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/query_range",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
||||
"step": []string{time.Minute.String()},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doLabelValues("mylabel"),
|
||||
inRes: []string{"val1", "val2"},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/label/mylabel/values",
|
||||
res: model.LabelValues{"val1", "val2"},
|
||||
},
|
||||
|
||||
{
|
||||
do: doLabelValues("mylabel"),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/label/mylabel/values",
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doSeries("up", testTime.Add(-time.Minute), testTime),
|
||||
inRes: []map[string]string{
|
||||
{
|
||||
"__name__": "up",
|
||||
"job": "prometheus",
|
||||
"instance": "localhost:9090"},
|
||||
},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/series",
|
||||
reqParam: url.Values{
|
||||
"match": []string{"up"},
|
||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
res: []model.LabelSet{
|
||||
model.LabelSet{
|
||||
"__name__": "up",
|
||||
"job": "prometheus",
|
||||
"instance": "localhost:9090",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
do: doSeries("up", testTime.Add(-time.Minute), testTime),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/series",
|
||||
reqParam: url.Values{
|
||||
"match": []string{"up"},
|
||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doSnapshot(true),
|
||||
inRes: map[string]string{
|
||||
"name": "20171210T211224Z-2be650b6d019eb54",
|
||||
},
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/admin/tsdb/snapshot",
|
||||
reqParam: url.Values{
|
||||
"skip_head": []string{"true"},
|
||||
},
|
||||
res: SnapshotResult{
|
||||
Name: "20171210T211224Z-2be650b6d019eb54",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
do: doSnapshot(true),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/admin/tsdb/snapshot",
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doCleanTombstones(),
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/admin/tsdb/clean_tombstones",
|
||||
},
|
||||
|
||||
{
|
||||
do: doCleanTombstones(),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/admin/tsdb/clean_tombstones",
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doDeleteSeries("up", testTime.Add(-time.Minute), testTime),
|
||||
inRes: []map[string]string{
|
||||
{
|
||||
"__name__": "up",
|
||||
"job": "prometheus",
|
||||
"instance": "localhost:9090"},
|
||||
},
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/admin/tsdb/delete_series",
|
||||
reqParam: url.Values{
|
||||
"match": []string{"up"},
|
||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
do: doDeleteSeries("up", testTime.Add(-time.Minute), testTime),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/admin/tsdb/delete_series",
|
||||
reqParam: url.Values{
|
||||
"match": []string{"up"},
|
||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doConfig(),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/status/config",
|
||||
inRes: map[string]string{
|
||||
"yaml": "<content of the loaded config file in YAML>",
|
||||
},
|
||||
res: ConfigResult{
|
||||
YAML: "<content of the loaded config file in YAML>",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
do: doConfig(),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/status/config",
|
||||
inErr: fmt.Errorf("some error"),
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doFlags(),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/status/flags",
|
||||
inRes: map[string]string{
|
||||
"alertmanager.notification-queue-capacity": "10000",
|
||||
"alertmanager.timeout": "10s",
|
||||
"log.level": "info",
|
||||
"query.lookback-delta": "5m",
|
||||
"query.max-concurrency": "20",
|
||||
},
|
||||
res: FlagsResult{
|
||||
"alertmanager.notification-queue-capacity": "10000",
|
||||
"alertmanager.timeout": "10s",
|
||||
"log.level": "info",
|
||||
"query.lookback-delta": "5m",
|
||||
"query.max-concurrency": "20",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
do: doFlags(),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/status/flags",
|
||||
inErr: fmt.Errorf("some error"),
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doAlertManagers(),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/alertmanagers",
|
||||
inRes: map[string]interface{}{
|
||||
"activeAlertManagers": []map[string]string{
|
||||
{
|
||||
"url": "http://127.0.0.1:9091/api/v1/alerts",
|
||||
},
|
||||
},
|
||||
"droppedAlertManagers": []map[string]string{
|
||||
{
|
||||
"url": "http://127.0.0.1:9092/api/v1/alerts",
|
||||
},
|
||||
},
|
||||
},
|
||||
res: AlertManagersResult{
|
||||
Active: []AlertManager{
|
||||
{
|
||||
URL: "http://127.0.0.1:9091/api/v1/alerts",
|
||||
},
|
||||
},
|
||||
Dropped: []AlertManager{
|
||||
{
|
||||
URL: "http://127.0.0.1:9092/api/v1/alerts",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
do: doAlertManagers(),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/alertmanagers",
|
||||
inErr: fmt.Errorf("some error"),
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doTargets(),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/targets",
|
||||
inRes: map[string]interface{}{
|
||||
"activeTargets": []map[string]interface{}{
|
||||
{
|
||||
"discoveredLabels": map[string]string{
|
||||
"__address__": "127.0.0.1:9090",
|
||||
"__metrics_path__": "/metrics",
|
||||
"__scheme__": "http",
|
||||
"job": "prometheus",
|
||||
},
|
||||
"labels": map[string]string{
|
||||
"instance": "127.0.0.1:9090",
|
||||
"job": "prometheus",
|
||||
},
|
||||
"scrapeUrl": "http://127.0.0.1:9090",
|
||||
"lastError": "error while scraping target",
|
||||
"lastScrape": testTime.UTC().Format(time.RFC3339Nano),
|
||||
"health": "up",
|
||||
},
|
||||
},
|
||||
"droppedTargets": []map[string]interface{}{
|
||||
{
|
||||
"discoveredLabels": map[string]string{
|
||||
"__address__": "127.0.0.1:9100",
|
||||
"__metrics_path__": "/metrics",
|
||||
"__scheme__": "http",
|
||||
"job": "node",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
res: TargetsResult{
|
||||
Active: []ActiveTarget{
|
||||
{
|
||||
DiscoveredLabels: model.LabelSet{
|
||||
"__address__": "127.0.0.1:9090",
|
||||
"__metrics_path__": "/metrics",
|
||||
"__scheme__": "http",
|
||||
"job": "prometheus",
|
||||
},
|
||||
Labels: model.LabelSet{
|
||||
"instance": "127.0.0.1:9090",
|
||||
"job": "prometheus",
|
||||
},
|
||||
ScrapeURL: "http://127.0.0.1:9090",
|
||||
LastError: "error while scraping target",
|
||||
LastScrape: testTime.UTC(),
|
||||
Health: HealthGood,
|
||||
},
|
||||
},
|
||||
Dropped: []DroppedTarget{
|
||||
{
|
||||
DiscoveredLabels: model.LabelSet{
|
||||
"__address__": "127.0.0.1:9100",
|
||||
"__metrics_path__": "/metrics",
|
||||
"__scheme__": "http",
|
||||
"job": "node",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
do: doTargets(),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/targets",
|
||||
inErr: fmt.Errorf("some error"),
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
}
|
||||
|
||||
var tests []apiTest
|
||||
tests = append(tests, queryTests...)
|
||||
|
||||
for _, test := range tests {
|
||||
client.curTest = test
|
||||
|
||||
res, err := test.do()
|
||||
|
||||
if test.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("expected error %q but got none", test.err)
|
||||
continue
|
||||
}
|
||||
if err.Error() != test.err.Error() {
|
||||
t.Errorf("unexpected error: want %s, got %s", test.err, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(res, test.res) {
|
||||
t.Errorf("unexpected result: want %v, got %v", test.res, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testClient struct {
|
||||
*testing.T
|
||||
|
||||
ch chan apiClientTest
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
type apiClientTest struct {
|
||||
code int
|
||||
response interface{}
|
||||
expected string
|
||||
err *Error
|
||||
}
|
||||
|
||||
func (c *testClient) URL(ep string, args map[string]string) *url.URL {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
if ctx == nil {
|
||||
c.Fatalf("context was not passed down")
|
||||
}
|
||||
if req != c.req {
|
||||
c.Fatalf("request was not passed down")
|
||||
}
|
||||
|
||||
test := <-c.ch
|
||||
|
||||
var b []byte
|
||||
var err error
|
||||
|
||||
switch v := test.response.(type) {
|
||||
case string:
|
||||
b = []byte(v)
|
||||
default:
|
||||
b, err = json.Marshal(v)
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
resp := &http.Response{
|
||||
StatusCode: test.code,
|
||||
}
|
||||
|
||||
return resp, b, nil
|
||||
}
|
||||
|
||||
func TestAPIClientDo(t *testing.T) {
|
||||
tests := []apiClientTest{
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "error",
|
||||
Data: json.RawMessage(`null`),
|
||||
ErrorType: ErrBadData,
|
||||
Error: "failed",
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrBadData,
|
||||
Msg: "failed",
|
||||
},
|
||||
code: statusAPIError,
|
||||
expected: `null`,
|
||||
},
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "error",
|
||||
Data: json.RawMessage(`"test"`),
|
||||
ErrorType: ErrTimeout,
|
||||
Error: "timed out",
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrTimeout,
|
||||
Msg: "timed out",
|
||||
},
|
||||
code: statusAPIError,
|
||||
expected: `test`,
|
||||
},
|
||||
{
|
||||
response: "bad json",
|
||||
err: &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "bad response code 500",
|
||||
},
|
||||
code: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "error",
|
||||
Data: json.RawMessage(`null`),
|
||||
ErrorType: ErrBadData,
|
||||
Error: "end timestamp must not be before start time",
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrBadData,
|
||||
Msg: "end timestamp must not be before start time",
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
response: "bad json",
|
||||
err: &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "invalid character 'b' looking for beginning of value",
|
||||
},
|
||||
code: statusAPIError,
|
||||
},
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "success",
|
||||
Data: json.RawMessage(`"test"`),
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "inconsistent body for response code",
|
||||
},
|
||||
code: statusAPIError,
|
||||
},
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "success",
|
||||
Data: json.RawMessage(`"test"`),
|
||||
ErrorType: ErrTimeout,
|
||||
Error: "timed out",
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "inconsistent body for response code",
|
||||
},
|
||||
code: statusAPIError,
|
||||
},
|
||||
{
|
||||
response: &apiResponse{
|
||||
Status: "error",
|
||||
Data: json.RawMessage(`"test"`),
|
||||
ErrorType: ErrTimeout,
|
||||
Error: "timed out",
|
||||
},
|
||||
err: &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "inconsistent body for response code",
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
tc := &testClient{
|
||||
T: t,
|
||||
ch: make(chan apiClientTest, 1),
|
||||
req: &http.Request{},
|
||||
}
|
||||
client := &apiClient{tc}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
tc.ch <- test
|
||||
|
||||
_, body, err := client.Do(context.Background(), tc.req)
|
||||
|
||||
if test.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("expected error %q but got none", test.err)
|
||||
continue
|
||||
}
|
||||
if test.err.Error() != err.Error() {
|
||||
t.Errorf("unexpected error: want %q, got %q", test.err, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpeceted error %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
want, got := test.expected, string(body)
|
||||
if want != got {
|
||||
t.Errorf("unexpected body: want %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package prometheus
|
||||
|
||||
import "testing"
|
||||
|
||||
type collectorDescribedByCollect struct {
|
||||
cnt Counter
|
||||
gge Gauge
|
||||
}
|
||||
|
||||
func (c collectorDescribedByCollect) Collect(ch chan<- Metric) {
|
||||
ch <- c.cnt
|
||||
ch <- c.gge
|
||||
}
|
||||
|
||||
func (c collectorDescribedByCollect) Describe(ch chan<- *Desc) {
|
||||
DescribeByCollect(c, ch)
|
||||
}
|
||||
|
||||
func TestDescribeByCollect(t *testing.T) {
|
||||
|
||||
goodCollector := collectorDescribedByCollect{
|
||||
cnt: NewCounter(CounterOpts{Name: "c1", Help: "help c1"}),
|
||||
gge: NewGauge(GaugeOpts{Name: "g1", Help: "help g1"}),
|
||||
}
|
||||
collidingCollector := collectorDescribedByCollect{
|
||||
cnt: NewCounter(CounterOpts{Name: "c2", Help: "help c2"}),
|
||||
gge: NewGauge(GaugeOpts{Name: "g1", Help: "help g1"}),
|
||||
}
|
||||
inconsistentCollector := collectorDescribedByCollect{
|
||||
cnt: NewCounter(CounterOpts{Name: "c3", Help: "help c3"}),
|
||||
gge: NewGauge(GaugeOpts{Name: "c3", Help: "help inconsistent"}),
|
||||
}
|
||||
|
||||
reg := NewPedanticRegistry()
|
||||
|
||||
if err := reg.Register(goodCollector); err != nil {
|
||||
t.Error("registration failed:", err)
|
||||
}
|
||||
if err := reg.Register(collidingCollector); err == nil {
|
||||
t.Error("registration unexpectedly succeeded")
|
||||
}
|
||||
if err := reg.Register(inconsistentCollector); err == nil {
|
||||
t.Error("registration unexpectedly succeeded")
|
||||
}
|
||||
|
||||
if _, err := reg.Gather(); err != nil {
|
||||
t.Error("gathering failed:", err)
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewDescInvalidLabelValues(t *testing.T) {
|
||||
desc := NewDesc(
|
||||
"sample_label",
|
||||
"sample label",
|
||||
nil,
|
||||
Labels{"a": "\xFF"},
|
||||
)
|
||||
if desc.err == nil {
|
||||
t.Errorf("NewDesc: expected error because: %s", desc.err)
|
||||
}
|
||||
}
|
93
vendor/github.com/prometheus/client_golang/prometheus/example_clustermanager_test.go
generated
vendored
93
vendor/github.com/prometheus/client_golang/prometheus/example_clustermanager_test.go
generated
vendored
71
vendor/github.com/prometheus/client_golang/prometheus/example_timer_complex_test.go
generated
vendored
71
vendor/github.com/prometheus/client_golang/prometheus/example_timer_complex_test.go
generated
vendored
@ -0,0 +1,71 @@
|
||||
// Copyright 2014 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 (
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
// apiRequestDuration tracks the duration separate for each HTTP status
|
||||
// class (1xx, 2xx, ...). This creates a fair amount of time series on
|
||||
// the Prometheus server. Usually, you would track the duration of
|
||||
// serving HTTP request without partitioning by outcome. Do something
|
||||
// like this only if needed. Also note how only status classes are
|
||||
// tracked, not every single status code. The latter would create an
|
||||
// even larger amount of time series. Request counters partitioned by
|
||||
// status code are usually OK as each counter only creates one time
|
||||
// series. Histograms are way more expensive, so partition with care and
|
||||
// only where you really need separate latency tracking. Partitioning by
|
||||
// status class is only an example. In concrete cases, other partitions
|
||||
// might make more sense.
|
||||
apiRequestDuration = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "api_request_duration_seconds",
|
||||
Help: "Histogram for the request duration of the public API, partitioned by status class.",
|
||||
Buckets: prometheus.ExponentialBuckets(0.1, 1.5, 5),
|
||||
},
|
||||
[]string{"status_class"},
|
||||
)
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
status := http.StatusOK
|
||||
// The ObserverFunc gets called by the deferred ObserveDuration and
|
||||
// decides which Histogram's Observe method is called.
|
||||
timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) {
|
||||
switch {
|
||||
case status >= 500: // Server error.
|
||||
apiRequestDuration.WithLabelValues("5xx").Observe(v)
|
||||
case status >= 400: // Client error.
|
||||
apiRequestDuration.WithLabelValues("4xx").Observe(v)
|
||||
case status >= 300: // Redirection.
|
||||
apiRequestDuration.WithLabelValues("3xx").Observe(v)
|
||||
case status >= 200: // Success.
|
||||
apiRequestDuration.WithLabelValues("2xx").Observe(v)
|
||||
default: // Informational.
|
||||
apiRequestDuration.WithLabelValues("1xx").Observe(v)
|
||||
}
|
||||
}))
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
// Handle the request. Set status accordingly.
|
||||
// ...
|
||||
}
|
||||
|
||||
func ExampleTimer_complex() {
|
||||
http.HandleFunc("/api", handler)
|
||||
}
|
48
vendor/github.com/prometheus/client_golang/prometheus/example_timer_gauge_test.go
generated
vendored
48
vendor/github.com/prometheus/client_golang/prometheus/example_timer_gauge_test.go
generated
vendored
@ -0,0 +1,48 @@
|
||||
// Copyright 2014 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 (
|
||||
"os"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
// If a function is called rarely (i.e. not more often than scrapes
|
||||
// happen) or ideally only once (like in a batch job), it can make sense
|
||||
// to use a Gauge for timing the function call. For timing a batch job
|
||||
// and pushing the result to a Pushgateway, see also the comprehensive
|
||||
// example in the push package.
|
||||
funcDuration = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "example_function_duration_seconds",
|
||||
Help: "Duration of the last call of an example function.",
|
||||
})
|
||||
)
|
||||
|
||||
func run() error {
|
||||
// The Set method of the Gauge is used to observe the duration.
|
||||
timer := prometheus.NewTimer(prometheus.ObserverFunc(funcDuration.Set))
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
// Do something. Return errors as encountered. The use of 'defer' above
|
||||
// makes sure the function is still timed properly.
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExampleTimer_gauge() {
|
||||
if err := run(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
// Copyright 2014 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 (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
requestDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Name: "example_request_duration_seconds",
|
||||
Help: "Histogram for the runtime of a simple example function.",
|
||||
Buckets: prometheus.LinearBuckets(0.01, 0.01, 10),
|
||||
})
|
||||
)
|
||||
|
||||
func ExampleTimer() {
|
||||
// timer times this example function. It uses a Histogram, but a Summary
|
||||
// would also work, as both implement Observer. Check out
|
||||
// https://prometheus.io/docs/practices/histograms/ for differences.
|
||||
timer := prometheus.NewTimer(requestDuration)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
// Do something here that takes time.
|
||||
time.Sleep(time.Duration(rand.NormFloat64()*10000+50000) * time.Microsecond)
|
||||
}
|
@ -0,0 +1,282 @@
|
||||
// Copyright 2016 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 graphite provides a bridge to push Prometheus metrics to a Graphite
|
||||
// server.
|
||||
package graphite
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/expfmt"
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultInterval = 15 * time.Second
|
||||
millisecondsPerSecond = 1000
|
||||
)
|
||||
|
||||
// HandlerErrorHandling defines how a Handler serving metrics will handle
|
||||
// errors.
|
||||
type HandlerErrorHandling int
|
||||
|
||||
// These constants cause handlers serving metrics to behave as described if
|
||||
// errors are encountered.
|
||||
const (
|
||||
// Ignore errors and try to push as many metrics to Graphite as possible.
|
||||
ContinueOnError HandlerErrorHandling = iota
|
||||
|
||||
// Abort the push to Graphite upon the first error encountered.
|
||||
AbortOnError
|
||||
)
|
||||
|
||||
// Config defines the Graphite bridge config.
|
||||
type Config struct {
|
||||
// The url to push data to. Required.
|
||||
URL string
|
||||
|
||||
// The prefix for the pushed Graphite metrics. Defaults to empty string.
|
||||
Prefix string
|
||||
|
||||
// The interval to use for pushing data to Graphite. Defaults to 15 seconds.
|
||||
Interval time.Duration
|
||||
|
||||
// The timeout for pushing metrics to Graphite. Defaults to 15 seconds.
|
||||
Timeout time.Duration
|
||||
|
||||
// The Gatherer to use for metrics. Defaults to prometheus.DefaultGatherer.
|
||||
Gatherer prometheus.Gatherer
|
||||
|
||||
// The logger that messages are written to. Defaults to no logging.
|
||||
Logger Logger
|
||||
|
||||
// ErrorHandling defines how errors are handled. Note that errors are
|
||||
// logged regardless of the configured ErrorHandling provided Logger
|
||||
// is not nil.
|
||||
ErrorHandling HandlerErrorHandling
|
||||
}
|
||||
|
||||
// Bridge pushes metrics to the configured Graphite server.
|
||||
type Bridge struct {
|
||||
url string
|
||||
prefix string
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
|
||||
errorHandling HandlerErrorHandling
|
||||
logger Logger
|
||||
|
||||
g prometheus.Gatherer
|
||||
}
|
||||
|
||||
// Logger is the minimal interface Bridge needs for logging. Note that
|
||||
// log.Logger from the standard library implements this interface, and it is
|
||||
// easy to implement by custom loggers, if they don't do so already anyway.
|
||||
type Logger interface {
|
||||
Println(v ...interface{})
|
||||
}
|
||||
|
||||
// NewBridge returns a pointer to a new Bridge struct.
|
||||
func NewBridge(c *Config) (*Bridge, error) {
|
||||
b := &Bridge{}
|
||||
|
||||
if c.URL == "" {
|
||||
return nil, errors.New("missing URL")
|
||||
}
|
||||
b.url = c.URL
|
||||
|
||||
if c.Gatherer == nil {
|
||||
b.g = prometheus.DefaultGatherer
|
||||
} else {
|
||||
b.g = c.Gatherer
|
||||
}
|
||||
|
||||
if c.Logger != nil {
|
||||
b.logger = c.Logger
|
||||
}
|
||||
|
||||
if c.Prefix != "" {
|
||||
b.prefix = c.Prefix
|
||||
}
|
||||
|
||||
var z time.Duration
|
||||
if c.Interval == z {
|
||||
b.interval = defaultInterval
|
||||
} else {
|
||||
b.interval = c.Interval
|
||||
}
|
||||
|
||||
if c.Timeout == z {
|
||||
b.timeout = defaultInterval
|
||||
} else {
|
||||
b.timeout = c.Timeout
|
||||
}
|
||||
|
||||
b.errorHandling = c.ErrorHandling
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Run starts the event loop that pushes Prometheus metrics to Graphite at the
|
||||
// configured interval.
|
||||
func (b *Bridge) Run(ctx context.Context) {
|
||||
ticker := time.NewTicker(b.interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := b.Push(); err != nil && b.logger != nil {
|
||||
b.logger.Println("error pushing to Graphite:", err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Push pushes Prometheus metrics to the configured Graphite server.
|
||||
func (b *Bridge) Push() error {
|
||||
mfs, err := b.g.Gather()
|
||||
if err != nil || len(mfs) == 0 {
|
||||
switch b.errorHandling {
|
||||
case AbortOnError:
|
||||
return err
|
||||
case ContinueOnError:
|
||||
if b.logger != nil {
|
||||
b.logger.Println("continue on error:", err)
|
||||
}
|
||||
default:
|
||||
panic("unrecognized error handling value")
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := net.DialTimeout("tcp", b.url, b.timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
return writeMetrics(conn, mfs, b.prefix, model.Now())
|
||||
}
|
||||
|
||||
func writeMetrics(w io.Writer, mfs []*dto.MetricFamily, prefix string, now model.Time) error {
|
||||
vec, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{
|
||||
Timestamp: now,
|
||||
}, mfs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := bufio.NewWriter(w)
|
||||
for _, s := range vec {
|
||||
for _, c := range prefix {
|
||||
if _, err := buf.WriteRune(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := buf.WriteByte('.'); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeMetric(buf, s.Metric); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprintf(buf, " %g %d\n", s.Value, int64(s.Timestamp)/millisecondsPerSecond); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := buf.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeMetric(buf *bufio.Writer, m model.Metric) error {
|
||||
metricName, hasName := m[model.MetricNameLabel]
|
||||
numLabels := len(m) - 1
|
||||
if !hasName {
|
||||
numLabels = len(m)
|
||||
}
|
||||
|
||||
labelStrings := make([]string, 0, numLabels)
|
||||
for label, value := range m {
|
||||
if label != model.MetricNameLabel {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s %s", string(label), string(value)))
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
switch numLabels {
|
||||
case 0:
|
||||
if hasName {
|
||||
return writeSanitized(buf, string(metricName))
|
||||
}
|
||||
default:
|
||||
sort.Strings(labelStrings)
|
||||
if err = writeSanitized(buf, string(metricName)); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range labelStrings {
|
||||
if err = buf.WriteByte('.'); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = writeSanitized(buf, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeSanitized(buf *bufio.Writer, s string) error {
|
||||
prevUnderscore := false
|
||||
|
||||
for _, c := range s {
|
||||
c = replaceInvalidRune(c)
|
||||
if c == '_' {
|
||||
if prevUnderscore {
|
||||
continue
|
||||
}
|
||||
prevUnderscore = true
|
||||
} else {
|
||||
prevUnderscore = false
|
||||
}
|
||||
if _, err := buf.WriteRune(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func replaceInvalidRune(c rune) rune {
|
||||
if c == ' ' {
|
||||
return '.'
|
||||
}
|
||||
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == ':' || c == '-' || (c >= '0' && c <= '9')) {
|
||||
return '_'
|
||||
}
|
||||
return c
|
||||
}
|
338
vendor/github.com/prometheus/client_golang/prometheus/graphite/bridge_test.go
generated
vendored
338
vendor/github.com/prometheus/client_golang/prometheus/graphite/bridge_test.go
generated
vendored
@ -0,0 +1,338 @@
|
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package graphite
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func TestSanitize(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in, out string
|
||||
}{
|
||||
{in: "hello", out: "hello"},
|
||||
{in: "hE/l1o", out: "hE_l1o"},
|
||||
{in: "he,*ll(.o", out: "he_ll_o"},
|
||||
{in: "hello_there%^&", out: "hello_there_"},
|
||||
{in: "hell-.o", out: "hell-_o"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := bufio.NewWriter(&buf)
|
||||
|
||||
for i, tc := range testCases {
|
||||
if err := writeSanitized(w, tc.in); err != nil {
|
||||
t.Fatalf("write failed: %v", err)
|
||||
}
|
||||
if err := w.Flush(); err != nil {
|
||||
t.Fatalf("flush failed: %v", err)
|
||||
}
|
||||
|
||||
if want, got := tc.out, buf.String(); want != got {
|
||||
t.Fatalf("test case index %d: got sanitized string %s, want %s", i, got, want)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteSummary(t *testing.T) {
|
||||
sumVec := prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
Name: "name",
|
||||
Help: "docstring",
|
||||
ConstLabels: prometheus.Labels{"constname": "constvalue"},
|
||||
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
|
||||
},
|
||||
[]string{"labelname"},
|
||||
)
|
||||
|
||||
sumVec.WithLabelValues("val1").Observe(float64(10))
|
||||
sumVec.WithLabelValues("val1").Observe(float64(20))
|
||||
sumVec.WithLabelValues("val1").Observe(float64(30))
|
||||
sumVec.WithLabelValues("val2").Observe(float64(20))
|
||||
sumVec.WithLabelValues("val2").Observe(float64(30))
|
||||
sumVec.WithLabelValues("val2").Observe(float64(40))
|
||||
|
||||
reg := prometheus.NewRegistry()
|
||||
reg.MustRegister(sumVec)
|
||||
|
||||
mfs, err := reg.Gather()
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
prefix string
|
||||
}{
|
||||
{prefix: "prefix"},
|
||||
{prefix: "pre/fix"},
|
||||
{prefix: "pre.fix"},
|
||||
}
|
||||
|
||||
const want = `%s.name.constname.constvalue.labelname.val1.quantile.0_5 20 1477043
|
||||
%s.name.constname.constvalue.labelname.val1.quantile.0_9 30 1477043
|
||||
%s.name.constname.constvalue.labelname.val1.quantile.0_99 30 1477043
|
||||
%s.name_sum.constname.constvalue.labelname.val1 60 1477043
|
||||
%s.name_count.constname.constvalue.labelname.val1 3 1477043
|
||||
%s.name.constname.constvalue.labelname.val2.quantile.0_5 30 1477043
|
||||
%s.name.constname.constvalue.labelname.val2.quantile.0_9 40 1477043
|
||||
%s.name.constname.constvalue.labelname.val2.quantile.0_99 40 1477043
|
||||
%s.name_sum.constname.constvalue.labelname.val2 90 1477043
|
||||
%s.name_count.constname.constvalue.labelname.val2 3 1477043
|
||||
`
|
||||
for i, tc := range testCases {
|
||||
|
||||
now := model.Time(1477043083)
|
||||
var buf bytes.Buffer
|
||||
err = writeMetrics(&buf, mfs, tc.prefix, now)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
wantWithPrefix := fmt.Sprintf(want,
|
||||
tc.prefix, tc.prefix, tc.prefix, tc.prefix, tc.prefix,
|
||||
tc.prefix, tc.prefix, tc.prefix, tc.prefix, tc.prefix,
|
||||
)
|
||||
if got := buf.String(); wantWithPrefix != got {
|
||||
t.Fatalf("test case index %d: wanted \n%s\n, got \n%s\n", i, wantWithPrefix, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteHistogram(t *testing.T) {
|
||||
histVec := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "name",
|
||||
Help: "docstring",
|
||||
ConstLabels: prometheus.Labels{"constname": "constvalue"},
|
||||
Buckets: []float64{0.01, 0.02, 0.05, 0.1},
|
||||
},
|
||||
[]string{"labelname"},
|
||||
)
|
||||
|
||||
histVec.WithLabelValues("val1").Observe(float64(10))
|
||||
histVec.WithLabelValues("val1").Observe(float64(20))
|
||||
histVec.WithLabelValues("val1").Observe(float64(30))
|
||||
histVec.WithLabelValues("val2").Observe(float64(20))
|
||||
histVec.WithLabelValues("val2").Observe(float64(30))
|
||||
histVec.WithLabelValues("val2").Observe(float64(40))
|
||||
|
||||
reg := prometheus.NewRegistry()
|
||||
reg.MustRegister(histVec)
|
||||
|
||||
mfs, err := reg.Gather()
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
now := model.Time(1477043083)
|
||||
var buf bytes.Buffer
|
||||
err = writeMetrics(&buf, mfs, "prefix", now)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
want := `prefix.name_bucket.constname.constvalue.labelname.val1.le.0_01 0 1477043
|
||||
prefix.name_bucket.constname.constvalue.labelname.val1.le.0_02 0 1477043
|
||||
prefix.name_bucket.constname.constvalue.labelname.val1.le.0_05 0 1477043
|
||||
prefix.name_bucket.constname.constvalue.labelname.val1.le.0_1 0 1477043
|
||||
prefix.name_sum.constname.constvalue.labelname.val1 60 1477043
|
||||
prefix.name_count.constname.constvalue.labelname.val1 3 1477043
|
||||
prefix.name_bucket.constname.constvalue.labelname.val1.le._Inf 3 1477043
|
||||
prefix.name_bucket.constname.constvalue.labelname.val2.le.0_01 0 1477043
|
||||
prefix.name_bucket.constname.constvalue.labelname.val2.le.0_02 0 1477043
|
||||
prefix.name_bucket.constname.constvalue.labelname.val2.le.0_05 0 1477043
|
||||
prefix.name_bucket.constname.constvalue.labelname.val2.le.0_1 0 1477043
|
||||
prefix.name_sum.constname.constvalue.labelname.val2 90 1477043
|
||||
prefix.name_count.constname.constvalue.labelname.val2 3 1477043
|
||||
prefix.name_bucket.constname.constvalue.labelname.val2.le._Inf 3 1477043
|
||||
`
|
||||
if got := buf.String(); want != got {
|
||||
t.Fatalf("wanted \n%s\n, got \n%s\n", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToReader(t *testing.T) {
|
||||
cntVec := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "name",
|
||||
Help: "docstring",
|
||||
ConstLabels: prometheus.Labels{"constname": "constvalue"},
|
||||
},
|
||||
[]string{"labelname"},
|
||||
)
|
||||
cntVec.WithLabelValues("val1").Inc()
|
||||
cntVec.WithLabelValues("val2").Inc()
|
||||
|
||||
reg := prometheus.NewRegistry()
|
||||
reg.MustRegister(cntVec)
|
||||
|
||||
want := `prefix.name.constname.constvalue.labelname.val1 1 1477043
|
||||
prefix.name.constname.constvalue.labelname.val2 1 1477043
|
||||
`
|
||||
mfs, err := reg.Gather()
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
now := model.Time(1477043083)
|
||||
var buf bytes.Buffer
|
||||
err = writeMetrics(&buf, mfs, "prefix", now)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
if got := buf.String(); want != got {
|
||||
t.Fatalf("wanted \n%s\n, got \n%s\n", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPush(t *testing.T) {
|
||||
reg := prometheus.NewRegistry()
|
||||
cntVec := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "name",
|
||||
Help: "docstring",
|
||||
ConstLabels: prometheus.Labels{"constname": "constvalue"},
|
||||
},
|
||||
[]string{"labelname"},
|
||||
)
|
||||
cntVec.WithLabelValues("val1").Inc()
|
||||
cntVec.WithLabelValues("val2").Inc()
|
||||
reg.MustRegister(cntVec)
|
||||
|
||||
host := "localhost"
|
||||
port := ":56789"
|
||||
b, err := NewBridge(&Config{
|
||||
URL: host + port,
|
||||
Gatherer: reg,
|
||||
Prefix: "prefix",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("error creating bridge: %v", err)
|
||||
}
|
||||
|
||||
nmg, err := newMockGraphite(port)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating mock graphite: %v", err)
|
||||
}
|
||||
defer nmg.Close()
|
||||
|
||||
err = b.Push()
|
||||
if err != nil {
|
||||
t.Fatalf("error pushing: %v", err)
|
||||
}
|
||||
|
||||
wants := []string{
|
||||
"prefix.name.constname.constvalue.labelname.val1 1",
|
||||
"prefix.name.constname.constvalue.labelname.val2 1",
|
||||
}
|
||||
|
||||
select {
|
||||
case got := <-nmg.readc:
|
||||
for _, want := range wants {
|
||||
matched, err := regexp.MatchString(want, got)
|
||||
if err != nil {
|
||||
t.Fatalf("error pushing: %v", err)
|
||||
}
|
||||
if !matched {
|
||||
t.Fatalf("missing metric:\nno match for %s received by server:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
return
|
||||
case err := <-nmg.errc:
|
||||
t.Fatalf("error reading push: %v", err)
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
t.Fatalf("no result from graphite server")
|
||||
}
|
||||
}
|
||||
|
||||
func newMockGraphite(port string) (*mockGraphite, error) {
|
||||
readc := make(chan string)
|
||||
errc := make(chan error)
|
||||
ln, err := net.Listen("tcp", port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
errc <- err
|
||||
}
|
||||
var b bytes.Buffer
|
||||
io.Copy(&b, conn)
|
||||
readc <- b.String()
|
||||
}()
|
||||
|
||||
return &mockGraphite{
|
||||
readc: readc,
|
||||
errc: errc,
|
||||
Listener: ln,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type mockGraphite struct {
|
||||
readc chan string
|
||||
errc chan error
|
||||
|
||||
net.Listener
|
||||
}
|
||||
|
||||
func ExampleBridge() {
|
||||
b, err := NewBridge(&Config{
|
||||
URL: "graphite.example.org:3099",
|
||||
Gatherer: prometheus.DefaultGatherer,
|
||||
Prefix: "prefix",
|
||||
Interval: 15 * time.Second,
|
||||
Timeout: 10 * time.Second,
|
||||
ErrorHandling: AbortOnError,
|
||||
Logger: log.New(os.Stdout, "graphite bridge: ", log.Lshortfile),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Start something in a goroutine that uses metrics.
|
||||
}()
|
||||
|
||||
// Push initial metrics to Graphite. Fail fast if the push fails.
|
||||
if err := b.Push(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create a Context to control stopping the Run() loop that pushes
|
||||
// metrics to Graphite.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Start pushing metrics to Graphite in the Run() loop.
|
||||
b.Run(ctx)
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
// metricSorter is a sortable slice of *dto.Metric.
|
||||
type metricSorter []*dto.Metric
|
||||
|
||||
func (s metricSorter) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s metricSorter) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s metricSorter) Less(i, j int) bool {
|
||||
if len(s[i].Label) != len(s[j].Label) {
|
||||
// This should not happen. The metrics are
|
||||
// inconsistent. However, we have to deal with the fact, as
|
||||
// people might use custom collectors or metric family injection
|
||||
// to create inconsistent metrics. So let's simply compare the
|
||||
// number of labels in this case. That will still yield
|
||||
// reproducible sorting.
|
||||
return len(s[i].Label) < len(s[j].Label)
|
||||
}
|
||||
for n, lp := range s[i].Label {
|
||||
vi := lp.GetValue()
|
||||
vj := s[j].Label[n].GetValue()
|
||||
if vi != vj {
|
||||
return vi < vj
|
||||
}
|
||||
}
|
||||
|
||||
// We should never arrive here. Multiple metrics with the same
|
||||
// label set in the same scrape will lead to undefined ingestion
|
||||
// behavior. However, as above, we have to provide stable sorting
|
||||
// here, even for inconsistent metrics. So sort equal metrics
|
||||
// by their timestamp, with missing timestamps (implying "now")
|
||||
// coming last.
|
||||
if s[i].TimestampMs == nil {
|
||||
return false
|
||||
}
|
||||
if s[j].TimestampMs == nil {
|
||||
return true
|
||||
}
|
||||
return s[i].GetTimestampMs() < s[j].GetTimestampMs()
|
||||
}
|
||||
|
||||
// NormalizeMetricFamilies returns a MetricFamily slice with empty
|
||||
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
|
||||
// the slice, with the contained Metrics sorted within each MetricFamily.
|
||||
func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
|
||||
for _, mf := range metricFamiliesByName {
|
||||
sort.Sort(metricSorter(mf.Metric))
|
||||
}
|
||||
names := make([]string, 0, len(metricFamiliesByName))
|
||||
for name, mf := range metricFamiliesByName {
|
||||
if len(mf.Metric) > 0 {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
result := make([]*dto.MetricFamily, 0, len(names))
|
||||
for _, name := range names {
|
||||
result = append(result, metricFamiliesByName[name])
|
||||
}
|
||||
return result
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// Labels represents a collection of label name -> value mappings. This type is
|
||||
// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
|
||||
// metric vector Collectors, e.g.:
|
||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
||||
//
|
||||
// The other use-case is the specification of constant label pairs in Opts or to
|
||||
// create a Desc.
|
||||
type Labels map[string]string
|
||||
|
||||
// reservedLabelPrefix is a prefix which is not legal in user-supplied
|
||||
// label names.
|
||||
const reservedLabelPrefix = "__"
|
||||
|
||||
var errInconsistentCardinality = errors.New("inconsistent label cardinality")
|
||||
|
||||
func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
|
||||
if len(labels) != expectedNumberOfValues {
|
||||
return errInconsistentCardinality
|
||||
}
|
||||
|
||||
for name, val := range labels {
|
||||
if !utf8.ValidString(val) {
|
||||
return fmt.Errorf("label %s: value %q is not valid UTF-8", name, val)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLabelValues(vals []string, expectedNumberOfValues int) error {
|
||||
if len(vals) != expectedNumberOfValues {
|
||||
return errInconsistentCardinality
|
||||
}
|
||||
|
||||
for _, val := range vals {
|
||||
if !utf8.ValidString(val) {
|
||||
return fmt.Errorf("label value %q is not valid UTF-8", val)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkLabelName(l string) bool {
|
||||
return model.LabelName(l).IsValid() && !strings.HasPrefix(l, reservedLabelPrefix)
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
// Copyright 2017 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
|
||||
|
||||
// Observer is the interface that wraps the Observe method, which is used by
|
||||
// Histogram and Summary to add observations.
|
||||
type Observer interface {
|
||||
Observe(float64)
|
||||
}
|
||||
|
||||
// The ObserverFunc type is an adapter to allow the use of ordinary
|
||||
// functions as Observers. If f is a function with the appropriate
|
||||
// signature, ObserverFunc(f) is an Observer that calls f.
|
||||
//
|
||||
// This adapter is usually used in connection with the Timer type, and there are
|
||||
// two general use cases:
|
||||
//
|
||||
// The most common one is to use a Gauge as the Observer for a Timer.
|
||||
// See the "Gauge" Timer example.
|
||||
//
|
||||
// The more advanced use case is to create a function that dynamically decides
|
||||
// which Observer to use for observing the duration. See the "Complex" Timer
|
||||
// example.
|
||||
type ObserverFunc func(float64)
|
||||
|
||||
// Observe calls f(value). It implements Observer.
|
||||
func (f ObserverFunc) Observe(value float64) {
|
||||
f(value)
|
||||
}
|
||||
|
||||
// ObserverVec is an interface implemented by `HistogramVec` and `SummaryVec`.
|
||||
type ObserverVec interface {
|
||||
GetMetricWith(Labels) (Observer, error)
|
||||
GetMetricWithLabelValues(lvs ...string) (Observer, error)
|
||||
With(Labels) Observer
|
||||
WithLabelValues(...string) Observer
|
||||
CurryWith(Labels) (ObserverVec, error)
|
||||
MustCurryWith(Labels) ObserverVec
|
||||
|
||||
Collector
|
||||
}
|
77
vendor/github.com/prometheus/client_golang/prometheus/process_collector_test.go
generated
vendored
77
vendor/github.com/prometheus/client_golang/prometheus/process_collector_test.go
generated
vendored
@ -0,0 +1,223 @@
|
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package promauto provides constructors for the usual Prometheus metrics that
|
||||
// return them already registered with the global registry
|
||||
// (prometheus.DefaultRegisterer). This allows very compact code, avoiding any
|
||||
// references to the registry altogether, but all the constructors in this
|
||||
// package will panic if the registration fails.
|
||||
//
|
||||
// The following example is a complete program to create a histogram of normally
|
||||
// distributed random numbers from the math/rand package:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "math/rand"
|
||||
// "net/http"
|
||||
//
|
||||
// "github.com/prometheus/client_golang/prometheus"
|
||||
// "github.com/prometheus/client_golang/prometheus/promauto"
|
||||
// "github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
// )
|
||||
//
|
||||
// var histogram = promauto.NewHistogram(prometheus.HistogramOpts{
|
||||
// Name: "random_numbers",
|
||||
// Help: "A histogram of normally distributed random numbers.",
|
||||
// Buckets: prometheus.LinearBuckets(-3, .1, 61),
|
||||
// })
|
||||
//
|
||||
// func Random() {
|
||||
// for {
|
||||
// histogram.Observe(rand.NormFloat64())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// go Random()
|
||||
// http.Handle("/metrics", promhttp.Handler())
|
||||
// http.ListenAndServe(":1971", nil)
|
||||
// }
|
||||
//
|
||||
// Prometheus's version of a minimal hello-world program:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "net/http"
|
||||
//
|
||||
// "github.com/prometheus/client_golang/prometheus"
|
||||
// "github.com/prometheus/client_golang/prometheus/promauto"
|
||||
// "github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// http.Handle("/", promhttp.InstrumentHandlerCounter(
|
||||
// promauto.NewCounterVec(
|
||||
// prometheus.CounterOpts{
|
||||
// Name: "hello_requests_total",
|
||||
// Help: "Total number of hello-world requests by HTTP code.",
|
||||
// },
|
||||
// []string{"code"},
|
||||
// ),
|
||||
// http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// fmt.Fprint(w, "Hello, world!")
|
||||
// }),
|
||||
// ))
|
||||
// http.Handle("/metrics", promhttp.Handler())
|
||||
// http.ListenAndServe(":1971", nil)
|
||||
// }
|
||||
//
|
||||
// This appears very handy. So why are these constructors locked away in a
|
||||
// separate package? There are two caveats:
|
||||
//
|
||||
// First, in more complex programs, global state is often quite problematic.
|
||||
// That's the reason why the metrics constructors in the prometheus package do
|
||||
// not interact with the global prometheus.DefaultRegisterer on their own. You
|
||||
// are free to use the Register or MustRegister functions to register them with
|
||||
// the global prometheus.DefaultRegisterer, but you could as well choose a local
|
||||
// Registerer (usually created with prometheus.NewRegistry, but there are other
|
||||
// scenarios, e.g. testing).
|
||||
//
|
||||
// The second issue is that registration may fail, e.g. if a metric inconsistent
|
||||
// with the newly to be registered one is already registered. But how to signal
|
||||
// and handle a panic in the automatic registration with the default registry?
|
||||
// The only way is panicking. While panicking on invalid input provided by the
|
||||
// programmer is certainly fine, things are a bit more subtle in this case: You
|
||||
// might just add another package to the program, and that package (in its init
|
||||
// function) happens to register a metric with the same name as your code. Now,
|
||||
// all of a sudden, either your code or the code of the newly imported package
|
||||
// panics, depending on initialization order, without any opportunity to handle
|
||||
// the case gracefully. Even worse is a scenario where registration happens
|
||||
// later during the runtime (e.g. upon loading some kind of plugin), where the
|
||||
// panic could be triggered long after the code has been deployed to
|
||||
// production. A possibility to panic should be explicitly called out by the
|
||||
// Must… idiom, cf. prometheus.MustRegister. But adding a separate set of
|
||||
// constructors in the prometheus package called MustRegisterNewCounterVec or
|
||||
// similar would be quite unwieldy. Adding an extra MustRegister method to each
|
||||
// metric, returning the registered metric, would result in nice code for those
|
||||
// using the method, but would pollute every single metric interface for
|
||||
// everybody avoiding the global registry.
|
||||
//
|
||||
// To address both issues, the problematic auto-registering and possibly
|
||||
// panicking constructors are all in this package with a clear warning
|
||||
// ahead. And whoever cares about avoiding global state and possibly panicking
|
||||
// function calls can simply ignore the existence of the promauto package
|
||||
// altogether.
|
||||
//
|
||||
// A final note: There is a similar case in the net/http package of the standard
|
||||
// library. It has DefaultServeMux as a global instance of ServeMux, and the
|
||||
// Handle function acts on it, panicking if a handler for the same pattern has
|
||||
// already been registered. However, one might argue that the whole HTTP routing
|
||||
// is usually set up closely together in the same package or file, while
|
||||
// Prometheus metrics tend to be spread widely over the codebase, increasing the
|
||||
// chance of surprising registration failures. Furthermore, the use of global
|
||||
// state in net/http has been criticized widely, and some avoid it altogether.
|
||||
package promauto
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
// NewCounter works like the function of the same name in the prometheus package
|
||||
// but it automatically registers the Counter with the
|
||||
// prometheus.DefaultRegisterer. If the registration fails, NewCounter panics.
|
||||
func NewCounter(opts prometheus.CounterOpts) prometheus.Counter {
|
||||
c := prometheus.NewCounter(opts)
|
||||
prometheus.MustRegister(c)
|
||||
return c
|
||||
}
|
||||
|
||||
// NewCounterVec works like the function of the same name in the prometheus
|
||||
// package but it automatically registers the CounterVec with the
|
||||
// prometheus.DefaultRegisterer. If the registration fails, NewCounterVec
|
||||
// panics.
|
||||
func NewCounterVec(opts prometheus.CounterOpts, labelNames []string) *prometheus.CounterVec {
|
||||
c := prometheus.NewCounterVec(opts, labelNames)
|
||||
prometheus.MustRegister(c)
|
||||
return c
|
||||
}
|
||||
|
||||
// NewCounterFunc works like the function of the same name in the prometheus
|
||||
// package but it automatically registers the CounterFunc with the
|
||||
// prometheus.DefaultRegisterer. If the registration fails, NewCounterFunc
|
||||
// panics.
|
||||
func NewCounterFunc(opts prometheus.CounterOpts, function func() float64) prometheus.CounterFunc {
|
||||
g := prometheus.NewCounterFunc(opts, function)
|
||||
prometheus.MustRegister(g)
|
||||
return g
|
||||
}
|
||||
|
||||
// NewGauge works like the function of the same name in the prometheus package
|
||||
// but it automatically registers the Gauge with the
|
||||
// prometheus.DefaultRegisterer. If the registration fails, NewGauge panics.
|
||||
func NewGauge(opts prometheus.GaugeOpts) prometheus.Gauge {
|
||||
g := prometheus.NewGauge(opts)
|
||||
prometheus.MustRegister(g)
|
||||
return g
|
||||
}
|
||||
|
||||
// NewGaugeVec works like the function of the same name in the prometheus
|
||||
// package but it automatically registers the GaugeVec with the
|
||||
// prometheus.DefaultRegisterer. If the registration fails, NewGaugeVec panics.
|
||||
func NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *prometheus.GaugeVec {
|
||||
g := prometheus.NewGaugeVec(opts, labelNames)
|
||||
prometheus.MustRegister(g)
|
||||
return g
|
||||
}
|
||||
|
||||
// NewGaugeFunc works like the function of the same name in the prometheus
|
||||
// package but it automatically registers the GaugeFunc with the
|
||||
// prometheus.DefaultRegisterer. If the registration fails, NewGaugeFunc panics.
|
||||
func NewGaugeFunc(opts prometheus.GaugeOpts, function func() float64) prometheus.GaugeFunc {
|
||||
g := prometheus.NewGaugeFunc(opts, function)
|
||||
prometheus.MustRegister(g)
|
||||
return g
|
||||
}
|
||||
|
||||
// NewSummary works like the function of the same name in the prometheus package
|
||||
// but it automatically registers the Summary with the
|
||||
// prometheus.DefaultRegisterer. If the registration fails, NewSummary panics.
|
||||
func NewSummary(opts prometheus.SummaryOpts) prometheus.Summary {
|
||||
s := prometheus.NewSummary(opts)
|
||||
prometheus.MustRegister(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// NewSummaryVec works like the function of the same name in the prometheus
|
||||
// package but it automatically registers the SummaryVec with the
|
||||
// prometheus.DefaultRegisterer. If the registration fails, NewSummaryVec
|
||||
// panics.
|
||||
func NewSummaryVec(opts prometheus.SummaryOpts, labelNames []string) *prometheus.SummaryVec {
|
||||
s := prometheus.NewSummaryVec(opts, labelNames)
|
||||
prometheus.MustRegister(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// NewHistogram works like the function of the same name in the prometheus
|
||||
// package but it automatically registers the Histogram with the
|
||||
// prometheus.DefaultRegisterer. If the registration fails, NewHistogram panics.
|
||||
func NewHistogram(opts prometheus.HistogramOpts) prometheus.Histogram {
|
||||
h := prometheus.NewHistogram(opts)
|
||||
prometheus.MustRegister(h)
|
||||
return h
|
||||
}
|
||||
|
||||
// NewHistogramVec works like the function of the same name in the prometheus
|
||||
// package but it automatically registers the HistogramVec with the
|
||||
// prometheus.DefaultRegisterer. If the registration fails, NewHistogramVec
|
||||
// panics.
|
||||
func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *prometheus.HistogramVec {
|
||||
h := prometheus.NewHistogramVec(opts, labelNames)
|
||||
prometheus.MustRegister(h)
|
||||
return h
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package promhttp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
closeNotifier = 1 << iota
|
||||
flusher
|
||||
hijacker
|
||||
readerFrom
|
||||
pusher
|
||||
)
|
||||
|
||||
type delegator interface {
|
||||
http.ResponseWriter
|
||||
|
||||
Status() int
|
||||
Written() int64
|
||||
}
|
||||
|
||||
type responseWriterDelegator struct {
|
||||
http.ResponseWriter
|
||||
|
||||
handler, method string
|
||||
status int
|
||||
written int64
|
||||
wroteHeader bool
|
||||
observeWriteHeader func(int)
|
||||
}
|
||||
|
||||
func (r *responseWriterDelegator) Status() int {
|
||||
return r.status
|
||||
}
|
||||
|
||||
func (r *responseWriterDelegator) Written() int64 {
|
||||
return r.written
|
||||
}
|
||||
|
||||
func (r *responseWriterDelegator) WriteHeader(code int) {
|
||||
r.status = code
|
||||
r.wroteHeader = true
|
||||
r.ResponseWriter.WriteHeader(code)
|
||||
if r.observeWriteHeader != nil {
|
||||
r.observeWriteHeader(code)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
||||
if !r.wroteHeader {
|
||||
r.WriteHeader(http.StatusOK)
|
||||
}
|
||||
n, err := r.ResponseWriter.Write(b)
|
||||
r.written += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
type closeNotifierDelegator struct{ *responseWriterDelegator }
|
||||
type flusherDelegator struct{ *responseWriterDelegator }
|
||||
type hijackerDelegator struct{ *responseWriterDelegator }
|
||||
type readerFromDelegator struct{ *responseWriterDelegator }
|
||||
|
||||
func (d closeNotifierDelegator) CloseNotify() <-chan bool {
|
||||
return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
func (d flusherDelegator) Flush() {
|
||||
d.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
func (d hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return d.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
|
||||
if !d.wroteHeader {
|
||||
d.WriteHeader(http.StatusOK)
|
||||
}
|
||||
n, err := d.ResponseWriter.(io.ReaderFrom).ReadFrom(re)
|
||||
d.written += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
var pickDelegator = make([]func(*responseWriterDelegator) delegator, 32)
|
||||
|
||||
func init() {
|
||||
// TODO(beorn7): Code generation would help here.
|
||||
pickDelegator[0] = func(d *responseWriterDelegator) delegator { // 0
|
||||
return d
|
||||
}
|
||||
pickDelegator[closeNotifier] = func(d *responseWriterDelegator) delegator { // 1
|
||||
return closeNotifierDelegator{d}
|
||||
}
|
||||
pickDelegator[flusher] = func(d *responseWriterDelegator) delegator { // 2
|
||||
return flusherDelegator{d}
|
||||
}
|
||||
pickDelegator[flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 3
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{d, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[hijacker] = func(d *responseWriterDelegator) delegator { // 4
|
||||
return hijackerDelegator{d}
|
||||
}
|
||||
pickDelegator[hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 5
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Hijacker
|
||||
http.CloseNotifier
|
||||
}{d, hijackerDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 6
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
}{d, hijackerDelegator{d}, flusherDelegator{d}}
|
||||
}
|
||||
pickDelegator[hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 7
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{d, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[readerFrom] = func(d *responseWriterDelegator) delegator { // 8
|
||||
return readerFromDelegator{d}
|
||||
}
|
||||
pickDelegator[readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 9
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
io.ReaderFrom
|
||||
http.CloseNotifier
|
||||
}{d, readerFromDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 10
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
io.ReaderFrom
|
||||
http.Flusher
|
||||
}{d, readerFromDelegator{d}, flusherDelegator{d}}
|
||||
}
|
||||
pickDelegator[readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 11
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
io.ReaderFrom
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{d, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 12
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
io.ReaderFrom
|
||||
http.Hijacker
|
||||
}{d, readerFromDelegator{d}, hijackerDelegator{d}}
|
||||
}
|
||||
pickDelegator[readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 13
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
io.ReaderFrom
|
||||
http.Hijacker
|
||||
http.CloseNotifier
|
||||
}{d, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 14
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
io.ReaderFrom
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
}{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
|
||||
}
|
||||
pickDelegator[readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 15
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
io.ReaderFrom
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
}
|
181
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go
generated
vendored
181
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go
generated
vendored
@ -0,0 +1,181 @@
|
||||
// Copyright 2017 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.8
|
||||
|
||||
package promhttp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type pusherDelegator struct{ *responseWriterDelegator }
|
||||
|
||||
func (d pusherDelegator) Push(target string, opts *http.PushOptions) error {
|
||||
return d.ResponseWriter.(http.Pusher).Push(target, opts)
|
||||
}
|
||||
|
||||
func init() {
|
||||
pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16
|
||||
return pusherDelegator{d}
|
||||
}
|
||||
pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
http.CloseNotifier
|
||||
}{d, pusherDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+flusher] = func(d *responseWriterDelegator) delegator { // 18
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
http.Flusher
|
||||
}{d, pusherDelegator{d}, flusherDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 19
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{d, pusherDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+hijacker] = func(d *responseWriterDelegator) delegator { // 20
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
http.Hijacker
|
||||
}{d, pusherDelegator{d}, hijackerDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 21
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
http.Hijacker
|
||||
http.CloseNotifier
|
||||
}{d, pusherDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 22
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+readerFrom] = func(d *responseWriterDelegator) delegator { // 24
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
io.ReaderFrom
|
||||
}{d, pusherDelegator{d}, readerFromDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 25
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
io.ReaderFrom
|
||||
http.CloseNotifier
|
||||
}{d, pusherDelegator{d}, readerFromDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 26
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
io.ReaderFrom
|
||||
http.Flusher
|
||||
}{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 27
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
io.ReaderFrom
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 28
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
io.ReaderFrom
|
||||
http.Hijacker
|
||||
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 29
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
io.ReaderFrom
|
||||
http.Hijacker
|
||||
http.CloseNotifier
|
||||
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 30
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
io.ReaderFrom
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
|
||||
}
|
||||
pickDelegator[pusher+readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 31
|
||||
return struct {
|
||||
*responseWriterDelegator
|
||||
http.Pusher
|
||||
io.ReaderFrom
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||
}
|
||||
}
|
||||
|
||||
func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
|
||||
d := &responseWriterDelegator{
|
||||
ResponseWriter: w,
|
||||
observeWriteHeader: observeWriteHeaderFunc,
|
||||
}
|
||||
|
||||
id := 0
|
||||
if _, ok := w.(http.CloseNotifier); ok {
|
||||
id += closeNotifier
|
||||
}
|
||||
if _, ok := w.(http.Flusher); ok {
|
||||
id += flusher
|
||||
}
|
||||
if _, ok := w.(http.Hijacker); ok {
|
||||
id += hijacker
|
||||
}
|
||||
if _, ok := w.(io.ReaderFrom); ok {
|
||||
id += readerFrom
|
||||
}
|
||||
if _, ok := w.(http.Pusher); ok {
|
||||
id += pusher
|
||||
}
|
||||
|
||||
return pickDelegator[id](d)
|
||||
}
|
44
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go
generated
vendored
44
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go
generated
vendored
@ -0,0 +1,44 @@
|
||||
// Copyright 2017 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.8
|
||||
|
||||
package promhttp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
|
||||
d := &responseWriterDelegator{
|
||||
ResponseWriter: w,
|
||||
observeWriteHeader: observeWriteHeaderFunc,
|
||||
}
|
||||
|
||||
id := 0
|
||||
if _, ok := w.(http.CloseNotifier); ok {
|
||||
id += closeNotifier
|
||||
}
|
||||
if _, ok := w.(http.Flusher); ok {
|
||||
id += flusher
|
||||
}
|
||||
if _, ok := w.(http.Hijacker); ok {
|
||||
id += hijacker
|
||||
}
|
||||
if _, ok := w.(io.ReaderFrom); ok {
|
||||
id += readerFrom
|
||||
}
|
||||
|
||||
return pickDelegator[id](d)
|
||||
}
|
97
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
generated
vendored
97
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
generated
vendored
@ -0,0 +1,97 @@
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package promhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// The RoundTripperFunc type is an adapter to allow the use of ordinary
|
||||
// functions as RoundTrippers. If f is a function with the appropriate
|
||||
// signature, RountTripperFunc(f) is a RoundTripper that calls f.
|
||||
type RoundTripperFunc func(req *http.Request) (*http.Response, error)
|
||||
|
||||
// RoundTrip implements the RoundTripper interface.
|
||||
func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return rt(r)
|
||||
}
|
||||
|
||||
// InstrumentRoundTripperInFlight is a middleware that wraps the provided
|
||||
// http.RoundTripper. It sets the provided prometheus.Gauge to the number of
|
||||
// requests currently handled by the wrapped http.RoundTripper.
|
||||
//
|
||||
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||
func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc {
|
||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
gauge.Inc()
|
||||
defer gauge.Dec()
|
||||
return next.RoundTrip(r)
|
||||
})
|
||||
}
|
||||
|
||||
// InstrumentRoundTripperCounter is a middleware that wraps the provided
|
||||
// http.RoundTripper to observe the request result with the provided CounterVec.
|
||||
// The CounterVec must have zero, one, or two non-const non-curried labels. For
|
||||
// those, the only allowed label names are "code" and "method". The function
|
||||
// panics otherwise. Partitioning of the CounterVec happens by HTTP status code
|
||||
// and/or HTTP method if the respective instance label names are present in the
|
||||
// CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
|
||||
//
|
||||
// If the wrapped RoundTripper panics or returns a non-nil error, the Counter
|
||||
// is not incremented.
|
||||
//
|
||||
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc {
|
||||
code, method := checkLabels(counter)
|
||||
|
||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
resp, err := next.RoundTrip(r)
|
||||
if err == nil {
|
||||
counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc()
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
|
||||
// InstrumentRoundTripperDuration is a middleware that wraps the provided
|
||||
// http.RoundTripper to observe the request duration with the provided
|
||||
// ObserverVec. The ObserverVec must have zero, one, or two non-const
|
||||
// non-curried labels. For those, the only allowed label names are "code" and
|
||||
// "method". The function panics otherwise. The Observe method of the Observer
|
||||
// in the ObserverVec is called with the request duration in
|
||||
// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
|
||||
// respective instance label names are present in the ObserverVec. For
|
||||
// unpartitioned observations, use an ObserverVec with zero labels. Note that
|
||||
// partitioning of Histograms is expensive and should be used judiciously.
|
||||
//
|
||||
// If the wrapped RoundTripper panics or returns a non-nil error, no values are
|
||||
// reported.
|
||||
//
|
||||
// Note that this method is only guaranteed to never observe negative durations
|
||||
// if used with Go1.9+.
|
||||
func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc {
|
||||
code, method := checkLabels(obs)
|
||||
|
||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
start := time.Now()
|
||||
resp, err := next.RoundTrip(r)
|
||||
if err == nil {
|
||||
obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds())
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
144
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go
generated
vendored
144
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go
generated
vendored
@ -0,0 +1,144 @@
|
||||
// Copyright 2017 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.8
|
||||
|
||||
package promhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InstrumentTrace is used to offer flexibility in instrumenting the available
|
||||
// httptrace.ClientTrace hook functions. Each function is passed a float64
|
||||
// representing the time in seconds since the start of the http request. A user
|
||||
// may choose to use separately buckets Histograms, or implement custom
|
||||
// instance labels on a per function basis.
|
||||
type InstrumentTrace struct {
|
||||
GotConn func(float64)
|
||||
PutIdleConn func(float64)
|
||||
GotFirstResponseByte func(float64)
|
||||
Got100Continue func(float64)
|
||||
DNSStart func(float64)
|
||||
DNSDone func(float64)
|
||||
ConnectStart func(float64)
|
||||
ConnectDone func(float64)
|
||||
TLSHandshakeStart func(float64)
|
||||
TLSHandshakeDone func(float64)
|
||||
WroteHeaders func(float64)
|
||||
Wait100Continue func(float64)
|
||||
WroteRequest func(float64)
|
||||
}
|
||||
|
||||
// InstrumentRoundTripperTrace is a middleware that wraps the provided
|
||||
// RoundTripper and reports times to hook functions provided in the
|
||||
// InstrumentTrace struct. Hook functions that are not present in the provided
|
||||
// InstrumentTrace struct are ignored. Times reported to the hook functions are
|
||||
// time since the start of the request. Only with Go1.9+, those times are
|
||||
// guaranteed to never be negative. (Earlier Go versions are not using a
|
||||
// monotonic clock.) Note that partitioning of Histograms is expensive and
|
||||
// should be used judiciously.
|
||||
//
|
||||
// For hook functions that receive an error as an argument, no observations are
|
||||
// made in the event of a non-nil error value.
|
||||
//
|
||||
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||
func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
|
||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
start := time.Now()
|
||||
|
||||
trace := &httptrace.ClientTrace{
|
||||
GotConn: func(_ httptrace.GotConnInfo) {
|
||||
if it.GotConn != nil {
|
||||
it.GotConn(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
PutIdleConn: func(err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if it.PutIdleConn != nil {
|
||||
it.PutIdleConn(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
DNSStart: func(_ httptrace.DNSStartInfo) {
|
||||
if it.DNSStart != nil {
|
||||
it.DNSStart(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
DNSDone: func(_ httptrace.DNSDoneInfo) {
|
||||
if it.DNSDone != nil {
|
||||
it.DNSDone(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
ConnectStart: func(_, _ string) {
|
||||
if it.ConnectStart != nil {
|
||||
it.ConnectStart(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
ConnectDone: func(_, _ string, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if it.ConnectDone != nil {
|
||||
it.ConnectDone(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
GotFirstResponseByte: func() {
|
||||
if it.GotFirstResponseByte != nil {
|
||||
it.GotFirstResponseByte(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
Got100Continue: func() {
|
||||
if it.Got100Continue != nil {
|
||||
it.Got100Continue(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
TLSHandshakeStart: func() {
|
||||
if it.TLSHandshakeStart != nil {
|
||||
it.TLSHandshakeStart(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if it.TLSHandshakeDone != nil {
|
||||
it.TLSHandshakeDone(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
WroteHeaders: func() {
|
||||
if it.WroteHeaders != nil {
|
||||
it.WroteHeaders(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
Wait100Continue: func() {
|
||||
if it.Wait100Continue != nil {
|
||||
it.Wait100Continue(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
WroteRequest: func(_ httptrace.WroteRequestInfo) {
|
||||
if it.WroteRequest != nil {
|
||||
it.WroteRequest(time.Since(start).Seconds())
|
||||
}
|
||||
},
|
||||
}
|
||||
r = r.WithContext(httptrace.WithClientTrace(context.Background(), trace))
|
||||
|
||||
return next.RoundTrip(r)
|
||||
})
|
||||
}
|
195
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8_test.go
generated
vendored
195
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8_test.go
generated
vendored
@ -0,0 +1,195 @@
|
||||
// Copyright 2017 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.8
|
||||
|
||||
package promhttp
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func TestClientMiddlewareAPI(t *testing.T) {
|
||||
client := http.DefaultClient
|
||||
client.Timeout = 1 * time.Second
|
||||
|
||||
reg := prometheus.NewRegistry()
|
||||
|
||||
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "client_in_flight_requests",
|
||||
Help: "A gauge of in-flight requests for the wrapped client.",
|
||||
})
|
||||
|
||||
counter := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "client_api_requests_total",
|
||||
Help: "A counter for requests from the wrapped client.",
|
||||
},
|
||||
[]string{"code", "method"},
|
||||
)
|
||||
|
||||
dnsLatencyVec := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "dns_duration_seconds",
|
||||
Help: "Trace dns latency histogram.",
|
||||
Buckets: []float64{.005, .01, .025, .05},
|
||||
},
|
||||
[]string{"event"},
|
||||
)
|
||||
|
||||
tlsLatencyVec := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "tls_duration_seconds",
|
||||
Help: "Trace tls latency histogram.",
|
||||
Buckets: []float64{.05, .1, .25, .5},
|
||||
},
|
||||
[]string{"event"},
|
||||
)
|
||||
|
||||
histVec := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "request_duration_seconds",
|
||||
Help: "A histogram of request latencies.",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
},
|
||||
[]string{"method"},
|
||||
)
|
||||
|
||||
reg.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, histVec, inFlightGauge)
|
||||
|
||||
trace := &InstrumentTrace{
|
||||
DNSStart: func(t float64) {
|
||||
dnsLatencyVec.WithLabelValues("dns_start")
|
||||
},
|
||||
DNSDone: func(t float64) {
|
||||
dnsLatencyVec.WithLabelValues("dns_done")
|
||||
},
|
||||
TLSHandshakeStart: func(t float64) {
|
||||
tlsLatencyVec.WithLabelValues("tls_handshake_start")
|
||||
},
|
||||
TLSHandshakeDone: func(t float64) {
|
||||
tlsLatencyVec.WithLabelValues("tls_handshake_done")
|
||||
},
|
||||
}
|
||||
|
||||
client.Transport = InstrumentRoundTripperInFlight(inFlightGauge,
|
||||
InstrumentRoundTripperCounter(counter,
|
||||
InstrumentRoundTripperTrace(trace,
|
||||
InstrumentRoundTripperDuration(histVec, http.DefaultTransport),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
resp, err := client.Get("http://google.com")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
func ExampleInstrumentRoundTripperDuration() {
|
||||
client := http.DefaultClient
|
||||
client.Timeout = 1 * time.Second
|
||||
|
||||
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "client_in_flight_requests",
|
||||
Help: "A gauge of in-flight requests for the wrapped client.",
|
||||
})
|
||||
|
||||
counter := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "client_api_requests_total",
|
||||
Help: "A counter for requests from the wrapped client.",
|
||||
},
|
||||
[]string{"code", "method"},
|
||||
)
|
||||
|
||||
// dnsLatencyVec uses custom buckets based on expected dns durations.
|
||||
// It has an instance label "event", which is set in the
|
||||
// DNSStart and DNSDonehook functions defined in the
|
||||
// InstrumentTrace struct below.
|
||||
dnsLatencyVec := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "dns_duration_seconds",
|
||||
Help: "Trace dns latency histogram.",
|
||||
Buckets: []float64{.005, .01, .025, .05},
|
||||
},
|
||||
[]string{"event"},
|
||||
)
|
||||
|
||||
// tlsLatencyVec uses custom buckets based on expected tls durations.
|
||||
// It has an instance label "event", which is set in the
|
||||
// TLSHandshakeStart and TLSHandshakeDone hook functions defined in the
|
||||
// InstrumentTrace struct below.
|
||||
tlsLatencyVec := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "tls_duration_seconds",
|
||||
Help: "Trace tls latency histogram.",
|
||||
Buckets: []float64{.05, .1, .25, .5},
|
||||
},
|
||||
[]string{"event"},
|
||||
)
|
||||
|
||||
// histVec has no labels, making it a zero-dimensional ObserverVec.
|
||||
histVec := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "request_duration_seconds",
|
||||
Help: "A histogram of request latencies.",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
},
|
||||
[]string{},
|
||||
)
|
||||
|
||||
// Register all of the metrics in the standard registry.
|
||||
prometheus.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, histVec, inFlightGauge)
|
||||
|
||||
// Define functions for the available httptrace.ClientTrace hook
|
||||
// functions that we want to instrument.
|
||||
trace := &InstrumentTrace{
|
||||
DNSStart: func(t float64) {
|
||||
dnsLatencyVec.WithLabelValues("dns_start")
|
||||
},
|
||||
DNSDone: func(t float64) {
|
||||
dnsLatencyVec.WithLabelValues("dns_done")
|
||||
},
|
||||
TLSHandshakeStart: func(t float64) {
|
||||
tlsLatencyVec.WithLabelValues("tls_handshake_start")
|
||||
},
|
||||
TLSHandshakeDone: func(t float64) {
|
||||
tlsLatencyVec.WithLabelValues("tls_handshake_done")
|
||||
},
|
||||
}
|
||||
|
||||
// Wrap the default RoundTripper with middleware.
|
||||
roundTripper := InstrumentRoundTripperInFlight(inFlightGauge,
|
||||
InstrumentRoundTripperCounter(counter,
|
||||
InstrumentRoundTripperTrace(trace,
|
||||
InstrumentRoundTripperDuration(histVec, http.DefaultTransport),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// Set the RoundTripper on our client.
|
||||
client.Transport = roundTripper
|
||||
|
||||
resp, err := client.Get("http://google.com")
|
||||
if err != nil {
|
||||
log.Printf("error: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
}
|
447
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
generated
vendored
447
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
generated
vendored
@ -0,0 +1,447 @@
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package promhttp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// magicString is used for the hacky label test in checkLabels. Remove once fixed.
|
||||
const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
|
||||
|
||||
// InstrumentHandlerInFlight is a middleware that wraps the provided
|
||||
// http.Handler. It sets the provided prometheus.Gauge to the number of
|
||||
// requests currently handled by the wrapped http.Handler.
|
||||
//
|
||||
// See the example for InstrumentHandlerDuration for example usage.
|
||||
func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
g.Inc()
|
||||
defer g.Dec()
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// InstrumentHandlerDuration is a middleware that wraps the provided
|
||||
// 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
|
||||
// those, the only allowed label names are "code" and "method". The function
|
||||
// panics otherwise. The Observe method of the Observer in the ObserverVec is
|
||||
// called with the request duration in seconds. Partitioning happens by HTTP
|
||||
// status code and/or HTTP method if the respective instance label names are
|
||||
// present in the ObserverVec. For unpartitioned observations, use an
|
||||
// ObserverVec with zero labels. Note that 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 panics, no values are reported.
|
||||
//
|
||||
// Note that this method is only guaranteed to never observe negative durations
|
||||
// if used with Go1.9+.
|
||||
func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
|
||||
code, method := checkLabels(obs)
|
||||
|
||||
if code {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now()
|
||||
d := newDelegator(w, nil)
|
||||
next.ServeHTTP(d, r)
|
||||
|
||||
obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds())
|
||||
})
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now()
|
||||
next.ServeHTTP(w, r)
|
||||
obs.With(labels(code, method, r.Method, 0)).Observe(time.Since(now).Seconds())
|
||||
})
|
||||
}
|
||||
|
||||
// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
|
||||
// to observe the request result with the provided CounterVec. The CounterVec
|
||||
// must have zero, one, or two non-const non-curried labels. For those, the only
|
||||
// allowed label names are "code" and "method". The function panics
|
||||
// otherwise. Partitioning of the CounterVec happens by HTTP status code and/or
|
||||
// HTTP method if the respective instance label names are present in the
|
||||
// CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
|
||||
//
|
||||
// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
|
||||
//
|
||||
// If the wrapped Handler panics, the Counter is not incremented.
|
||||
//
|
||||
// See the example for InstrumentHandlerDuration for example usage.
|
||||
func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc {
|
||||
code, method := checkLabels(counter)
|
||||
|
||||
if code {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
d := newDelegator(w, nil)
|
||||
next.ServeHTTP(d, r)
|
||||
counter.With(labels(code, method, r.Method, d.Status())).Inc()
|
||||
})
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(w, r)
|
||||
counter.With(labels(code, method, r.Method, 0)).Inc()
|
||||
})
|
||||
}
|
||||
|
||||
// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
|
||||
// http.Handler to observe with the provided ObserverVec the request duration
|
||||
// until the response headers are written. The ObserverVec must have zero, one,
|
||||
// or two non-const non-curried labels. For those, the only allowed label names
|
||||
// are "code" and "method". The function panics otherwise. The Observe method of
|
||||
// the Observer in the ObserverVec is called with the request duration in
|
||||
// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
|
||||
// respective instance label names are present in the ObserverVec. For
|
||||
// unpartitioned observations, 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
|
||||
// reported.
|
||||
//
|
||||
// Note that this method is only guaranteed to never observe negative durations
|
||||
// if used with Go1.9+.
|
||||
//
|
||||
// See the example for InstrumentHandlerDuration for example usage.
|
||||
func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
|
||||
code, method := checkLabels(obs)
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now()
|
||||
d := newDelegator(w, func(status int) {
|
||||
obs.With(labels(code, method, r.Method, status)).Observe(time.Since(now).Seconds())
|
||||
})
|
||||
next.ServeHTTP(d, r)
|
||||
})
|
||||
}
|
||||
|
||||
// InstrumentHandlerRequestSize is a middleware that wraps the provided
|
||||
// http.Handler to observe the request size with the provided ObserverVec. The
|
||||
// ObserverVec must have zero, one, or two non-const non-curried labels. For
|
||||
// those, the only allowed label names are "code" and "method". The function
|
||||
// panics otherwise. The Observe method of the Observer in the ObserverVec is
|
||||
// called with the request size in bytes. Partitioning happens by HTTP status
|
||||
// code and/or HTTP method if the respective instance label names are present in
|
||||
// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
|
||||
// labels. Note that 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 panics, no values are reported.
|
||||
//
|
||||
// See the example for InstrumentHandlerDuration for example usage.
|
||||
func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
|
||||
code, method := checkLabels(obs)
|
||||
|
||||
if code {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
d := newDelegator(w, nil)
|
||||
next.ServeHTTP(d, r)
|
||||
size := computeApproximateRequestSize(r)
|
||||
obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size))
|
||||
})
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(w, r)
|
||||
size := computeApproximateRequestSize(r)
|
||||
obs.With(labels(code, method, r.Method, 0)).Observe(float64(size))
|
||||
})
|
||||
}
|
||||
|
||||
// InstrumentHandlerResponseSize is a middleware that wraps the provided
|
||||
// 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
|
||||
// those, the only allowed label names are "code" and "method". The function
|
||||
// panics otherwise. The Observe method of the Observer in the ObserverVec is
|
||||
// called with the response size in bytes. Partitioning happens by HTTP status
|
||||
// code and/or HTTP method if the respective instance label names are present in
|
||||
// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
|
||||
// labels. Note that 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 panics, no values are reported.
|
||||
//
|
||||
// See the example for InstrumentHandlerDuration for example usage.
|
||||
func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler {
|
||||
code, method := checkLabels(obs)
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
d := newDelegator(w, nil)
|
||||
next.ServeHTTP(d, r)
|
||||
obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written()))
|
||||
})
|
||||
}
|
||||
|
||||
func checkLabels(c prometheus.Collector) (code bool, method bool) {
|
||||
// TODO(beorn7): Remove this hacky way to check for instance labels
|
||||
// once Descriptors can have their dimensionality queried.
|
||||
var (
|
||||
desc *prometheus.Desc
|
||||
m prometheus.Metric
|
||||
pm dto.Metric
|
||||
lvs []string
|
||||
)
|
||||
|
||||
// Get the Desc from the Collector.
|
||||
descc := make(chan *prometheus.Desc, 1)
|
||||
c.Describe(descc)
|
||||
|
||||
select {
|
||||
case desc = <-descc:
|
||||
default:
|
||||
panic("no description provided by collector")
|
||||
}
|
||||
select {
|
||||
case <-descc:
|
||||
panic("more than one description provided by collector")
|
||||
default:
|
||||
}
|
||||
|
||||
close(descc)
|
||||
|
||||
// Create a ConstMetric with the Desc. Since we don't know how many
|
||||
// variable labels there are, try for as long as it needs.
|
||||
for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) {
|
||||
m, err = prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, lvs...)
|
||||
}
|
||||
|
||||
// Write out the metric into a proto message and look at the labels.
|
||||
// If the value is not the magicString, it is a constLabel, which doesn't interest us.
|
||||
// If the label is curried, it doesn't interest us.
|
||||
// In all other cases, only "code" or "method" is allowed.
|
||||
if err := m.Write(&pm); err != nil {
|
||||
panic("error checking metric for labels")
|
||||
}
|
||||
for _, label := range pm.Label {
|
||||
name, value := label.GetName(), label.GetValue()
|
||||
if value != magicString || isLabelCurried(c, name) {
|
||||
continue
|
||||
}
|
||||
switch name {
|
||||
case "code":
|
||||
code = true
|
||||
case "method":
|
||||
method = true
|
||||
default:
|
||||
panic("metric partitioned with non-supported labels")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isLabelCurried(c prometheus.Collector, label string) bool {
|
||||
// This is even hackier than the label test above.
|
||||
// We essentially try to curry again and see if it works.
|
||||
// But for that, we need to type-convert to the two
|
||||
// types we use here, ObserverVec or *CounterVec.
|
||||
switch v := c.(type) {
|
||||
case *prometheus.CounterVec:
|
||||
if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
|
||||
return false
|
||||
}
|
||||
case prometheus.ObserverVec:
|
||||
if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
panic("unsupported metric vec type")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// emptyLabels is a one-time allocation for non-partitioned metrics to avoid
|
||||
// unnecessary allocations on each request.
|
||||
var emptyLabels = prometheus.Labels{}
|
||||
|
||||
func labels(code, method bool, reqMethod string, status int) prometheus.Labels {
|
||||
if !(code || method) {
|
||||
return emptyLabels
|
||||
}
|
||||
labels := prometheus.Labels{}
|
||||
|
||||
if code {
|
||||
labels["code"] = sanitizeCode(status)
|
||||
}
|
||||
if method {
|
||||
labels["method"] = sanitizeMethod(reqMethod)
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
func computeApproximateRequestSize(r *http.Request) int {
|
||||
s := 0
|
||||
if r.URL != nil {
|
||||
s += len(r.URL.String())
|
||||
}
|
||||
|
||||
s += len(r.Method)
|
||||
s += len(r.Proto)
|
||||
for name, values := range r.Header {
|
||||
s += len(name)
|
||||
for _, value := range values {
|
||||
s += len(value)
|
||||
}
|
||||
}
|
||||
s += len(r.Host)
|
||||
|
||||
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
|
||||
|
||||
if r.ContentLength != -1 {
|
||||
s += int(r.ContentLength)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func sanitizeMethod(m string) string {
|
||||
switch m {
|
||||
case "GET", "get":
|
||||
return "get"
|
||||
case "PUT", "put":
|
||||
return "put"
|
||||
case "HEAD", "head":
|
||||
return "head"
|
||||
case "POST", "post":
|
||||
return "post"
|
||||
case "DELETE", "delete":
|
||||
return "delete"
|
||||
case "CONNECT", "connect":
|
||||
return "connect"
|
||||
case "OPTIONS", "options":
|
||||
return "options"
|
||||
case "NOTIFY", "notify":
|
||||
return "notify"
|
||||
default:
|
||||
return strings.ToLower(m)
|
||||
}
|
||||
}
|
||||
|
||||
// If the wrapped http.Handler has not set a status code, i.e. the value is
|
||||
// currently 0, santizeCode will return 200, for consistency with behavior in
|
||||
// the stdlib.
|
||||
func sanitizeCode(s int) string {
|
||||
switch s {
|
||||
case 100:
|
||||
return "100"
|
||||
case 101:
|
||||
return "101"
|
||||
|
||||
case 200, 0:
|
||||
return "200"
|
||||
case 201:
|
||||
return "201"
|
||||
case 202:
|
||||
return "202"
|
||||
case 203:
|
||||
return "203"
|
||||
case 204:
|
||||
return "204"
|
||||
case 205:
|
||||
return "205"
|
||||
case 206:
|
||||
return "206"
|
||||
|
||||
case 300:
|
||||
return "300"
|
||||
case 301:
|
||||
return "301"
|
||||
case 302:
|
||||
return "302"
|
||||
case 304:
|
||||
return "304"
|
||||
case 305:
|
||||
return "305"
|
||||
case 307:
|
||||
return "307"
|
||||
|
||||
case 400:
|
||||
return "400"
|
||||
case 401:
|
||||
return "401"
|
||||
case 402:
|
||||
return "402"
|
||||
case 403:
|
||||
return "403"
|
||||
case 404:
|
||||
return "404"
|
||||
case 405:
|
||||
return "405"
|
||||
case 406:
|
||||
return "406"
|
||||
case 407:
|
||||
return "407"
|
||||
case 408:
|
||||
return "408"
|
||||
case 409:
|
||||
return "409"
|
||||
case 410:
|
||||
return "410"
|
||||
case 411:
|
||||
return "411"
|
||||
case 412:
|
||||
return "412"
|
||||
case 413:
|
||||
return "413"
|
||||
case 414:
|
||||
return "414"
|
||||
case 415:
|
||||
return "415"
|
||||
case 416:
|
||||
return "416"
|
||||
case 417:
|
||||
return "417"
|
||||
case 418:
|
||||
return "418"
|
||||
|
||||
case 500:
|
||||
return "500"
|
||||
case 501:
|
||||
return "501"
|
||||
case 502:
|
||||
return "502"
|
||||
case 503:
|
||||
return "503"
|
||||
case 504:
|
||||
return "504"
|
||||
case 505:
|
||||
return "505"
|
||||
|
||||
case 428:
|
||||
return "428"
|
||||
case 429:
|
||||
return "429"
|
||||
case 431:
|
||||
return "431"
|
||||
case 511:
|
||||
return "511"
|
||||
|
||||
default:
|
||||
return strconv.Itoa(s)
|
||||
}
|
||||
}
|
401
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go
generated
vendored
401
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go
generated
vendored
@ -0,0 +1,401 @@
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package promhttp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func TestLabelCheck(t *testing.T) {
|
||||
scenarios := map[string]struct {
|
||||
varLabels []string
|
||||
constLabels []string
|
||||
curriedLabels []string
|
||||
ok bool
|
||||
}{
|
||||
"empty": {
|
||||
varLabels: []string{},
|
||||
constLabels: []string{},
|
||||
curriedLabels: []string{},
|
||||
ok: true,
|
||||
},
|
||||
"code as single var label": {
|
||||
varLabels: []string{"code"},
|
||||
constLabels: []string{},
|
||||
curriedLabels: []string{},
|
||||
ok: true,
|
||||
},
|
||||
"method as single var label": {
|
||||
varLabels: []string{"method"},
|
||||
constLabels: []string{},
|
||||
curriedLabels: []string{},
|
||||
ok: true,
|
||||
},
|
||||
"cade and method as var labels": {
|
||||
varLabels: []string{"method", "code"},
|
||||
constLabels: []string{},
|
||||
curriedLabels: []string{},
|
||||
ok: true,
|
||||
},
|
||||
"valid case with all labels used": {
|
||||
varLabels: []string{"code", "method"},
|
||||
constLabels: []string{"foo", "bar"},
|
||||
curriedLabels: []string{"dings", "bums"},
|
||||
ok: true,
|
||||
},
|
||||
"unsupported var label": {
|
||||
varLabels: []string{"foo"},
|
||||
constLabels: []string{},
|
||||
curriedLabels: []string{},
|
||||
ok: false,
|
||||
},
|
||||
"mixed var labels": {
|
||||
varLabels: []string{"method", "foo", "code"},
|
||||
constLabels: []string{},
|
||||
curriedLabels: []string{},
|
||||
ok: false,
|
||||
},
|
||||
"unsupported var label but curried": {
|
||||
varLabels: []string{},
|
||||
constLabels: []string{},
|
||||
curriedLabels: []string{"foo"},
|
||||
ok: true,
|
||||
},
|
||||
"mixed var labels but unsupported curried": {
|
||||
varLabels: []string{"code", "method"},
|
||||
constLabels: []string{},
|
||||
curriedLabels: []string{"foo"},
|
||||
ok: true,
|
||||
},
|
||||
"supported label as const and curry": {
|
||||
varLabels: []string{},
|
||||
constLabels: []string{"code"},
|
||||
curriedLabels: []string{"method"},
|
||||
ok: true,
|
||||
},
|
||||
"supported label as const and curry with unsupported as var": {
|
||||
varLabels: []string{"foo"},
|
||||
constLabels: []string{"code"},
|
||||
curriedLabels: []string{"method"},
|
||||
ok: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, sc := range scenarios {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
constLabels := prometheus.Labels{}
|
||||
for _, l := range sc.constLabels {
|
||||
constLabels[l] = "dummy"
|
||||
}
|
||||
c := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "c",
|
||||
Help: "c help",
|
||||
ConstLabels: constLabels,
|
||||
},
|
||||
append(sc.varLabels, sc.curriedLabels...),
|
||||
)
|
||||
o := prometheus.ObserverVec(prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "c",
|
||||
Help: "c help",
|
||||
ConstLabels: constLabels,
|
||||
},
|
||||
append(sc.varLabels, sc.curriedLabels...),
|
||||
))
|
||||
for _, l := range sc.curriedLabels {
|
||||
c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
|
||||
o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
|
||||
}
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if sc.ok {
|
||||
t.Error("unexpected panic:", err)
|
||||
}
|
||||
} else if !sc.ok {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
}()
|
||||
InstrumentHandlerCounter(c, nil)
|
||||
}()
|
||||
func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if sc.ok {
|
||||
t.Error("unexpected panic:", err)
|
||||
}
|
||||
} else if !sc.ok {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
}()
|
||||
InstrumentHandlerDuration(o, nil)
|
||||
}()
|
||||
if sc.ok {
|
||||
// Test if wantCode and wantMethod were detected correctly.
|
||||
var wantCode, wantMethod bool
|
||||
for _, l := range sc.varLabels {
|
||||
if l == "code" {
|
||||
wantCode = true
|
||||
}
|
||||
if l == "method" {
|
||||
wantMethod = true
|
||||
}
|
||||
}
|
||||
gotCode, gotMethod := checkLabels(c)
|
||||
if gotCode != wantCode {
|
||||
t.Errorf("wanted code=%t for counter, got code=%t", wantCode, gotCode)
|
||||
}
|
||||
if gotMethod != wantMethod {
|
||||
t.Errorf("wanted method=%t for counter, got method=%t", wantMethod, gotMethod)
|
||||
}
|
||||
gotCode, gotMethod = checkLabels(o)
|
||||
if gotCode != wantCode {
|
||||
t.Errorf("wanted code=%t for observer, got code=%t", wantCode, gotCode)
|
||||
}
|
||||
if gotMethod != wantMethod {
|
||||
t.Errorf("wanted method=%t for observer, got method=%t", wantMethod, gotMethod)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiddlewareAPI(t *testing.T) {
|
||||
reg := prometheus.NewRegistry()
|
||||
|
||||
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "in_flight_requests",
|
||||
Help: "A gauge of requests currently being served by the wrapped handler.",
|
||||
})
|
||||
|
||||
counter := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "api_requests_total",
|
||||
Help: "A counter for requests to the wrapped handler.",
|
||||
},
|
||||
[]string{"code", "method"},
|
||||
)
|
||||
|
||||
histVec := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "response_duration_seconds",
|
||||
Help: "A histogram of request latencies.",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
ConstLabels: prometheus.Labels{"handler": "api"},
|
||||
},
|
||||
[]string{"method"},
|
||||
)
|
||||
|
||||
writeHeaderVec := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "write_header_duration_seconds",
|
||||
Help: "A histogram of time to first write latencies.",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
ConstLabels: prometheus.Labels{"handler": "api"},
|
||||
},
|
||||
[]string{},
|
||||
)
|
||||
|
||||
responseSize := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "push_request_size_bytes",
|
||||
Help: "A histogram of request sizes for requests.",
|
||||
Buckets: []float64{200, 500, 900, 1500},
|
||||
},
|
||||
[]string{},
|
||||
)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
reg.MustRegister(inFlightGauge, counter, histVec, responseSize, writeHeaderVec)
|
||||
|
||||
chain := InstrumentHandlerInFlight(inFlightGauge,
|
||||
InstrumentHandlerCounter(counter,
|
||||
InstrumentHandlerDuration(histVec,
|
||||
InstrumentHandlerTimeToWriteHeader(writeHeaderVec,
|
||||
InstrumentHandlerResponseSize(responseSize, handler),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
r, _ := http.NewRequest("GET", "www.example.com", nil)
|
||||
w := httptest.NewRecorder()
|
||||
chain.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func TestInstrumentTimeToFirstWrite(t *testing.T) {
|
||||
var i int
|
||||
dobs := &responseWriterDelegator{
|
||||
ResponseWriter: httptest.NewRecorder(),
|
||||
observeWriteHeader: func(status int) {
|
||||
i = status
|
||||
},
|
||||
}
|
||||
d := newDelegator(dobs, nil)
|
||||
|
||||
d.WriteHeader(http.StatusOK)
|
||||
|
||||
if i != http.StatusOK {
|
||||
t.Fatalf("failed to execute observeWriteHeader")
|
||||
}
|
||||
}
|
||||
|
||||
// testResponseWriter is an http.ResponseWriter that also implements
|
||||
// http.CloseNotifier, http.Flusher, and io.ReaderFrom.
|
||||
type testResponseWriter struct {
|
||||
closeNotifyCalled, flushCalled, readFromCalled bool
|
||||
}
|
||||
|
||||
func (t *testResponseWriter) Header() http.Header { return nil }
|
||||
func (t *testResponseWriter) Write([]byte) (int, error) { return 0, nil }
|
||||
func (t *testResponseWriter) WriteHeader(int) {}
|
||||
func (t *testResponseWriter) CloseNotify() <-chan bool {
|
||||
t.closeNotifyCalled = true
|
||||
return nil
|
||||
}
|
||||
func (t *testResponseWriter) Flush() { t.flushCalled = true }
|
||||
func (t *testResponseWriter) ReadFrom(io.Reader) (int64, error) {
|
||||
t.readFromCalled = true
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// testFlusher is an http.ResponseWriter that also implements http.Flusher.
|
||||
type testFlusher struct {
|
||||
flushCalled bool
|
||||
}
|
||||
|
||||
func (t *testFlusher) Header() http.Header { return nil }
|
||||
func (t *testFlusher) Write([]byte) (int, error) { return 0, nil }
|
||||
func (t *testFlusher) WriteHeader(int) {}
|
||||
func (t *testFlusher) Flush() { t.flushCalled = true }
|
||||
|
||||
func TestInterfaceUpgrade(t *testing.T) {
|
||||
w := &testResponseWriter{}
|
||||
d := newDelegator(w, nil)
|
||||
d.(http.CloseNotifier).CloseNotify()
|
||||
if !w.closeNotifyCalled {
|
||||
t.Error("CloseNotify not called")
|
||||
}
|
||||
d.(http.Flusher).Flush()
|
||||
if !w.flushCalled {
|
||||
t.Error("Flush not called")
|
||||
}
|
||||
d.(io.ReaderFrom).ReadFrom(nil)
|
||||
if !w.readFromCalled {
|
||||
t.Error("ReadFrom not called")
|
||||
}
|
||||
if _, ok := d.(http.Hijacker); ok {
|
||||
t.Error("delegator unexpectedly implements http.Hijacker")
|
||||
}
|
||||
|
||||
f := &testFlusher{}
|
||||
d = newDelegator(f, nil)
|
||||
if _, ok := d.(http.CloseNotifier); ok {
|
||||
t.Error("delegator unexpectedly implements http.CloseNotifier")
|
||||
}
|
||||
d.(http.Flusher).Flush()
|
||||
if !w.flushCalled {
|
||||
t.Error("Flush not called")
|
||||
}
|
||||
if _, ok := d.(io.ReaderFrom); ok {
|
||||
t.Error("delegator unexpectedly implements io.ReaderFrom")
|
||||
}
|
||||
if _, ok := d.(http.Hijacker); ok {
|
||||
t.Error("delegator unexpectedly implements http.Hijacker")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleInstrumentHandlerDuration() {
|
||||
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "in_flight_requests",
|
||||
Help: "A gauge of requests currently being served by the wrapped handler.",
|
||||
})
|
||||
|
||||
counter := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "api_requests_total",
|
||||
Help: "A counter for requests to the wrapped handler.",
|
||||
},
|
||||
[]string{"code", "method"},
|
||||
)
|
||||
|
||||
// duration is partitioned by the HTTP method and handler. It uses custom
|
||||
// buckets based on the expected request duration.
|
||||
duration := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "request_duration_seconds",
|
||||
Help: "A histogram of latencies for requests.",
|
||||
Buckets: []float64{.25, .5, 1, 2.5, 5, 10},
|
||||
},
|
||||
[]string{"handler", "method"},
|
||||
)
|
||||
|
||||
// responseSize has no labels, making it a zero-dimensional
|
||||
// ObserverVec.
|
||||
responseSize := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "response_size_bytes",
|
||||
Help: "A histogram of response sizes for requests.",
|
||||
Buckets: []float64{200, 500, 900, 1500},
|
||||
},
|
||||
[]string{},
|
||||
)
|
||||
|
||||
// Create the handlers that will be wrapped by the middleware.
|
||||
pushHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("Push"))
|
||||
})
|
||||
pullHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("Pull"))
|
||||
})
|
||||
|
||||
// Register all of the metrics in the standard registry.
|
||||
prometheus.MustRegister(inFlightGauge, counter, duration, responseSize)
|
||||
|
||||
// Instrument the handlers with all the metrics, injecting the "handler"
|
||||
// label by currying.
|
||||
pushChain := InstrumentHandlerInFlight(inFlightGauge,
|
||||
InstrumentHandlerDuration(duration.MustCurryWith(prometheus.Labels{"handler": "push"}),
|
||||
InstrumentHandlerCounter(counter,
|
||||
InstrumentHandlerResponseSize(responseSize, pushHandler),
|
||||
),
|
||||
),
|
||||
)
|
||||
pullChain := InstrumentHandlerInFlight(inFlightGauge,
|
||||
InstrumentHandlerDuration(duration.MustCurryWith(prometheus.Labels{"handler": "pull"}),
|
||||
InstrumentHandlerCounter(counter,
|
||||
InstrumentHandlerResponseSize(responseSize, pullHandler),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
http.Handle("/metrics", Handler())
|
||||
http.Handle("/push", pushChain)
|
||||
http.Handle("/pull", pullChain)
|
||||
|
||||
if err := http.ListenAndServe(":3000", nil); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package push
|
||||
|
||||
// This file contains only deprecated code. Remove after v0.9 is released.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/common/expfmt"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// FromGatherer triggers a metric collection by the provided Gatherer (which is
|
||||
// usually implemented by a prometheus.Registry) and pushes all gathered metrics
|
||||
// to the Pushgateway specified by url, using the provided job name and the
|
||||
// (optional) further grouping labels (the grouping map may be nil). See the
|
||||
// Pushgateway documentation for detailed implications of the job and other
|
||||
// grouping labels. Neither the job name nor any grouping label value may
|
||||
// contain a "/". The metrics pushed must not contain a job label of their own
|
||||
// nor any of the grouping labels.
|
||||
//
|
||||
// You can use just host:port or ip:port as url, in which case 'http://' is
|
||||
// added automatically. You can also include the schema in the URL. However, do
|
||||
// not include the '/metrics/jobs/...' part.
|
||||
//
|
||||
// Note that all previously pushed metrics with the same job and other grouping
|
||||
// labels will be replaced with the metrics pushed by this call. (It uses HTTP
|
||||
// method 'PUT' to push to the Pushgateway.)
|
||||
//
|
||||
// Deprecated: Please use a Pusher created with New instead.
|
||||
func FromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
|
||||
return push(job, grouping, url, g, "PUT")
|
||||
}
|
||||
|
||||
// AddFromGatherer works like FromGatherer, but only previously pushed metrics
|
||||
// with the same name (and the same job and other grouping labels) will be
|
||||
// replaced. (It uses HTTP method 'POST' to push to the Pushgateway.)
|
||||
//
|
||||
// Deprecated: Please use a Pusher created with New instead.
|
||||
func AddFromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
|
||||
return push(job, grouping, url, g, "POST")
|
||||
}
|
||||
|
||||
func push(job string, grouping map[string]string, pushURL string, g prometheus.Gatherer, method string) error {
|
||||
if !strings.Contains(pushURL, "://") {
|
||||
pushURL = "http://" + pushURL
|
||||
}
|
||||
if strings.HasSuffix(pushURL, "/") {
|
||||
pushURL = pushURL[:len(pushURL)-1]
|
||||
}
|
||||
|
||||
if strings.Contains(job, "/") {
|
||||
return fmt.Errorf("job contains '/': %s", job)
|
||||
}
|
||||
urlComponents := []string{url.QueryEscape(job)}
|
||||
for ln, lv := range grouping {
|
||||
if !model.LabelName(ln).IsValid() {
|
||||
return fmt.Errorf("grouping label has invalid name: %s", ln)
|
||||
}
|
||||
if strings.Contains(lv, "/") {
|
||||
return fmt.Errorf("value of grouping label %s contains '/': %s", ln, lv)
|
||||
}
|
||||
urlComponents = append(urlComponents, ln, lv)
|
||||
}
|
||||
pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/"))
|
||||
|
||||
mfs, err := g.Gather()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)
|
||||
// Check for pre-existing grouping labels:
|
||||
for _, mf := range mfs {
|
||||
for _, m := range mf.GetMetric() {
|
||||
for _, l := range m.GetLabel() {
|
||||
if l.GetName() == "job" {
|
||||
return fmt.Errorf("pushed metric %s (%s) already contains a job label", mf.GetName(), m)
|
||||
}
|
||||
if _, ok := grouping[l.GetName()]; ok {
|
||||
return fmt.Errorf(
|
||||
"pushed metric %s (%s) already contains grouping label %s",
|
||||
mf.GetName(), m, l.GetName(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
enc.Encode(mf)
|
||||
}
|
||||
req, err := http.NewRequest(method, pushURL, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set(contentTypeHeader, string(expfmt.FmtProtoDelim))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 202 {
|
||||
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, pushURL, body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Collectors works like FromGatherer, but it does not use a Gatherer. Instead,
|
||||
// it collects from the provided collectors directly. It is a convenient way to
|
||||
// push only a few metrics.
|
||||
//
|
||||
// Deprecated: Please use a Pusher created with New instead.
|
||||
func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
|
||||
return pushCollectors(job, grouping, url, "PUT", collectors...)
|
||||
}
|
||||
|
||||
// AddCollectors works like AddFromGatherer, but it does not use a Gatherer.
|
||||
// Instead, it collects from the provided collectors directly. It is a
|
||||
// convenient way to push only a few metrics.
|
||||
//
|
||||
// Deprecated: Please use a Pusher created with New instead.
|
||||
func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
|
||||
return pushCollectors(job, grouping, url, "POST", collectors...)
|
||||
}
|
||||
|
||||
func pushCollectors(job string, grouping map[string]string, url, method string, collectors ...prometheus.Collector) error {
|
||||
r := prometheus.NewRegistry()
|
||||
for _, collector := range collectors {
|
||||
if err := r.Register(collector); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return push(job, grouping, url, r, method)
|
||||
}
|
||||
|
||||
// HostnameGroupingKey returns a label map with the only entry
|
||||
// {instance="<hostname>"}. This can be conveniently used as the grouping
|
||||
// parameter if metrics should be pushed with the hostname as label. The
|
||||
// returned map is created upon each call so that the caller is free to add more
|
||||
// labels to the map.
|
||||
//
|
||||
// Deprecated: Usually, metrics pushed to the Pushgateway should not be
|
||||
// host-centric. (You would use https://github.com/prometheus/node_exporter in
|
||||
// that case.) If you have the need to add the hostname to the grouping key, you
|
||||
// are probably doing something wrong. See
|
||||
// https://prometheus.io/docs/practices/pushing/ for details.
|
||||
func HostnameGroupingKey() map[string]string {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return map[string]string{"instance": "unknown"}
|
||||
}
|
||||
return map[string]string{"instance": hostname}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
// Copyright 2016 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 push_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/push"
|
||||
)
|
||||
|
||||
var (
|
||||
completionTime = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "db_backup_last_completion_timestamp_seconds",
|
||||
Help: "The timestamp of the last completion of a DB backup, successful or not.",
|
||||
})
|
||||
successTime = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "db_backup_last_success_timestamp_seconds",
|
||||
Help: "The timestamp of the last successful completion of a DB backup.",
|
||||
})
|
||||
duration = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "db_backup_duration_seconds",
|
||||
Help: "The duration of the last DB backup in seconds.",
|
||||
})
|
||||
records = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "db_backup_records_processed",
|
||||
Help: "The number of records processed in the last DB backup.",
|
||||
})
|
||||
)
|
||||
|
||||
func performBackup() (int, error) {
|
||||
// Perform the backup and return the number of backed up records and any
|
||||
// applicable error.
|
||||
// ...
|
||||
return 42, nil
|
||||
}
|
||||
|
||||
func ExamplePusher_Add() {
|
||||
// We use a registry here to benefit from the consistency checks that
|
||||
// happen during registration.
|
||||
registry := prometheus.NewRegistry()
|
||||
registry.MustRegister(completionTime, duration, records)
|
||||
// Note that successTime is not registered.
|
||||
|
||||
pusher := push.New("http://pushgateway:9091", "db_backup").Gatherer(registry)
|
||||
|
||||
start := time.Now()
|
||||
n, err := performBackup()
|
||||
records.Set(float64(n))
|
||||
// Note that time.Since only uses a monotonic clock in Go1.9+.
|
||||
duration.Set(time.Since(start).Seconds())
|
||||
completionTime.SetToCurrentTime()
|
||||
if err != nil {
|
||||
fmt.Println("DB backup failed:", err)
|
||||
} else {
|
||||
// Add successTime to pusher only in case of success.
|
||||
// We could as well register it with the registry.
|
||||
// This example, however, demonstrates that you can
|
||||
// mix Gatherers and Collectors when handling a Pusher.
|
||||
pusher.Collector(successTime)
|
||||
successTime.SetToCurrentTime()
|
||||
}
|
||||
// Add is used here rather than Push to not delete a previously pushed
|
||||
// success timestamp in case of a failure of this backup.
|
||||
if err := pusher.Add(); err != nil {
|
||||
fmt.Println("Could not push to Pushgateway:", err)
|
||||
}
|
||||
}
|
213
vendor/github.com/prometheus/client_golang/prometheus/testutil/testutil_test.go
generated
vendored
213
vendor/github.com/prometheus/client_golang/prometheus/testutil/testutil_test.go
generated
vendored
@ -0,0 +1,213 @@
|
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type untypedCollector struct{}
|
||||
|
||||
func (u untypedCollector) Describe(c chan<- *prometheus.Desc) {
|
||||
c <- prometheus.NewDesc("name", "help", nil, nil)
|
||||
}
|
||||
|
||||
func (u untypedCollector) Collect(c chan<- prometheus.Metric) {
|
||||
c <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc("name", "help", nil, nil),
|
||||
prometheus.UntypedValue,
|
||||
2001,
|
||||
)
|
||||
}
|
||||
|
||||
func TestToFloat64(t *testing.T) {
|
||||
gaugeWithAValueSet := prometheus.NewGauge(prometheus.GaugeOpts{})
|
||||
gaugeWithAValueSet.Set(3.14)
|
||||
|
||||
counterVecWithOneElement := prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"foo"})
|
||||
counterVecWithOneElement.WithLabelValues("bar").Inc()
|
||||
|
||||
counterVecWithTwoElements := prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"foo"})
|
||||
counterVecWithTwoElements.WithLabelValues("bar").Add(42)
|
||||
counterVecWithTwoElements.WithLabelValues("baz").Inc()
|
||||
|
||||
histogramVecWithOneElement := prometheus.NewHistogramVec(prometheus.HistogramOpts{}, []string{"foo"})
|
||||
histogramVecWithOneElement.WithLabelValues("bar").Observe(2.7)
|
||||
|
||||
scenarios := map[string]struct {
|
||||
collector prometheus.Collector
|
||||
panics bool
|
||||
want float64
|
||||
}{
|
||||
"simple counter": {
|
||||
collector: prometheus.NewCounter(prometheus.CounterOpts{}),
|
||||
panics: false,
|
||||
want: 0,
|
||||
},
|
||||
"simple gauge": {
|
||||
collector: prometheus.NewGauge(prometheus.GaugeOpts{}),
|
||||
panics: false,
|
||||
want: 0,
|
||||
},
|
||||
"simple untyped": {
|
||||
collector: untypedCollector{},
|
||||
panics: false,
|
||||
want: 2001,
|
||||
},
|
||||
"simple histogram": {
|
||||
collector: prometheus.NewHistogram(prometheus.HistogramOpts{}),
|
||||
panics: true,
|
||||
},
|
||||
"simple summary": {
|
||||
collector: prometheus.NewSummary(prometheus.SummaryOpts{}),
|
||||
panics: true,
|
||||
},
|
||||
"simple gauge with an actual value set": {
|
||||
collector: gaugeWithAValueSet,
|
||||
panics: false,
|
||||
want: 3.14,
|
||||
},
|
||||
"counter vec with zero elements": {
|
||||
collector: prometheus.NewCounterVec(prometheus.CounterOpts{}, nil),
|
||||
panics: true,
|
||||
},
|
||||
"counter vec with one element": {
|
||||
collector: counterVecWithOneElement,
|
||||
panics: false,
|
||||
want: 1,
|
||||
},
|
||||
"counter vec with two elements": {
|
||||
collector: counterVecWithTwoElements,
|
||||
panics: true,
|
||||
},
|
||||
"histogram vec with one element": {
|
||||
collector: histogramVecWithOneElement,
|
||||
panics: true,
|
||||
},
|
||||
}
|
||||
|
||||
for n, s := range scenarios {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil && s.panics {
|
||||
t.Error("expected panic")
|
||||
} else if r != nil && !s.panics {
|
||||
t.Error("unexpected panic: ", r)
|
||||
}
|
||||
// Any other combination is the expected outcome.
|
||||
}()
|
||||
if got := ToFloat64(s.collector); got != s.want {
|
||||
t.Errorf("want %f, got %f", s.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectAndCompare(t *testing.T) {
|
||||
const metadata = `
|
||||
# HELP some_total A value that represents a counter.
|
||||
# TYPE some_total counter
|
||||
`
|
||||
|
||||
c := prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "some_total",
|
||||
Help: "A value that represents a counter.",
|
||||
ConstLabels: prometheus.Labels{
|
||||
"label1": "value1",
|
||||
},
|
||||
})
|
||||
c.Inc()
|
||||
|
||||
expected := `
|
||||
|
||||
some_total{ label1 = "value1" } 1
|
||||
`
|
||||
|
||||
if err := CollectAndCompare(c, strings.NewReader(metadata+expected), "some_total"); err != nil {
|
||||
t.Errorf("unexpected collecting result:\n%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoMetricFilter(t *testing.T) {
|
||||
const metadata = `
|
||||
# HELP some_total A value that represents a counter.
|
||||
# TYPE some_total counter
|
||||
`
|
||||
|
||||
c := prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "some_total",
|
||||
Help: "A value that represents a counter.",
|
||||
ConstLabels: prometheus.Labels{
|
||||
"label1": "value1",
|
||||
},
|
||||
})
|
||||
c.Inc()
|
||||
|
||||
expected := `
|
||||
some_total{label1="value1"} 1
|
||||
`
|
||||
|
||||
if err := CollectAndCompare(c, strings.NewReader(metadata+expected)); err != nil {
|
||||
t.Errorf("unexpected collecting result:\n%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricNotFound(t *testing.T) {
|
||||
const metadata = `
|
||||
# HELP some_other_metric A value that represents a counter.
|
||||
# TYPE some_other_metric counter
|
||||
`
|
||||
|
||||
c := prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "some_total",
|
||||
Help: "A value that represents a counter.",
|
||||
ConstLabels: prometheus.Labels{
|
||||
"label1": "value1",
|
||||
},
|
||||
})
|
||||
c.Inc()
|
||||
|
||||
expected := `
|
||||
some_other_metric{label1="value1"} 1
|
||||
`
|
||||
|
||||
expectedError := `
|
||||
metric output does not match expectation; want:
|
||||
|
||||
# HELP some_other_metric A value that represents a counter.
|
||||
# TYPE some_other_metric counter
|
||||
some_other_metric{label1="value1"} 1
|
||||
|
||||
|
||||
got:
|
||||
|
||||
# HELP some_total A value that represents a counter.
|
||||
# TYPE some_total counter
|
||||
some_total{label1="value1"} 1
|
||||
|
||||
`
|
||||
|
||||
err := CollectAndCompare(c, strings.NewReader(metadata+expected))
|
||||
if err == nil {
|
||||
t.Error("Expected error, got no error.")
|
||||
}
|
||||
|
||||
if err.Error() != expectedError {
|
||||
t.Errorf("Expected\n%#+v\nGot:\n%#+v\n", expectedError, err.Error())
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
// Copyright 2016 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package prometheus
|
||||
|
||||
import "time"
|
||||
|
||||
// Timer is a helper type to time functions. Use NewTimer to create new
|
||||
// instances.
|
||||
type Timer struct {
|
||||
begin time.Time
|
||||
observer Observer
|
||||
}
|
||||
|
||||
// NewTimer creates a new Timer. The provided Observer is used to observe a
|
||||
// duration in seconds. Timer is usually used to time a function call in the
|
||||
// following way:
|
||||
// func TimeMe() {
|
||||
// timer := NewTimer(myHistogram)
|
||||
// defer timer.ObserveDuration()
|
||||
// // Do actual work.
|
||||
// }
|
||||
func NewTimer(o Observer) *Timer {
|
||||
return &Timer{
|
||||
begin: time.Now(),
|
||||
observer: o,
|
||||
}
|
||||
}
|
||||
|
||||
// ObserveDuration records the duration passed since the Timer was created with
|
||||
// NewTimer. It calls the Observe method of the Observer provided during
|
||||
// construction with the duration in seconds as an argument. ObserveDuration is
|
||||
// usually called with a defer statement.
|
||||
//
|
||||
// Note that this method is only guaranteed to never observe negative durations
|
||||
// if used with Go1.9+.
|
||||
func (t *Timer) ObserveDuration() {
|
||||
if t.observer != nil {
|
||||
t.observer.Observe(time.Since(t.begin).Seconds())
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
// Copyright 2016 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
func TestTimerObserve(t *testing.T) {
|
||||
var (
|
||||
his = NewHistogram(HistogramOpts{Name: "test_histogram"})
|
||||
sum = NewSummary(SummaryOpts{Name: "test_summary"})
|
||||
gauge = NewGauge(GaugeOpts{Name: "test_gauge"})
|
||||
)
|
||||
|
||||
func() {
|
||||
hisTimer := NewTimer(his)
|
||||
sumTimer := NewTimer(sum)
|
||||
gaugeTimer := NewTimer(ObserverFunc(gauge.Set))
|
||||
defer hisTimer.ObserveDuration()
|
||||
defer sumTimer.ObserveDuration()
|
||||
defer gaugeTimer.ObserveDuration()
|
||||
}()
|
||||
|
||||
m := &dto.Metric{}
|
||||
his.Write(m)
|
||||
if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
|
||||
t.Errorf("want %d observations for histogram, got %d", want, got)
|
||||
}
|
||||
m.Reset()
|
||||
sum.Write(m)
|
||||
if want, got := uint64(1), m.GetSummary().GetSampleCount(); want != got {
|
||||
t.Errorf("want %d observations for summary, got %d", want, got)
|
||||
}
|
||||
m.Reset()
|
||||
gauge.Write(m)
|
||||
if got := m.GetGauge().GetValue(); got <= 0 {
|
||||
t.Errorf("want value > 0 for gauge, got %f", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimerEmpty(t *testing.T) {
|
||||
emptyTimer := NewTimer(nil)
|
||||
emptyTimer.ObserveDuration()
|
||||
// Do nothing, just demonstrate it works without panic.
|
||||
}
|
||||
|
||||
func TestTimerConditionalTiming(t *testing.T) {
|
||||
var (
|
||||
his = NewHistogram(HistogramOpts{
|
||||
Name: "test_histogram",
|
||||
})
|
||||
timeMe = true
|
||||
m = &dto.Metric{}
|
||||
)
|
||||
|
||||
timedFunc := func() {
|
||||
timer := NewTimer(ObserverFunc(func(v float64) {
|
||||
if timeMe {
|
||||
his.Observe(v)
|
||||
}
|
||||
}))
|
||||
defer timer.ObserveDuration()
|
||||
}
|
||||
|
||||
timedFunc() // This will time.
|
||||
his.Write(m)
|
||||
if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
|
||||
t.Errorf("want %d observations for histogram, got %d", want, got)
|
||||
}
|
||||
|
||||
timeMe = false
|
||||
timedFunc() // This will not time again.
|
||||
m.Reset()
|
||||
his.Write(m)
|
||||
if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
|
||||
t.Errorf("want %d observations for histogram, got %d", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimerByOutcome(t *testing.T) {
|
||||
var (
|
||||
his = NewHistogramVec(
|
||||
HistogramOpts{Name: "test_histogram"},
|
||||
[]string{"outcome"},
|
||||
)
|
||||
outcome = "foo"
|
||||
m = &dto.Metric{}
|
||||
)
|
||||
|
||||
timedFunc := func() {
|
||||
timer := NewTimer(ObserverFunc(func(v float64) {
|
||||
his.WithLabelValues(outcome).Observe(v)
|
||||
}))
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
if outcome == "foo" {
|
||||
outcome = "bar"
|
||||
return
|
||||
}
|
||||
outcome = "foo"
|
||||
}
|
||||
|
||||
timedFunc()
|
||||
his.WithLabelValues("foo").(Histogram).Write(m)
|
||||
if want, got := uint64(0), m.GetHistogram().GetSampleCount(); want != got {
|
||||
t.Errorf("want %d observations for 'foo' histogram, got %d", want, got)
|
||||
}
|
||||
m.Reset()
|
||||
his.WithLabelValues("bar").(Histogram).Write(m)
|
||||
if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
|
||||
t.Errorf("want %d observations for 'bar' histogram, got %d", want, got)
|
||||
}
|
||||
|
||||
timedFunc()
|
||||
m.Reset()
|
||||
his.WithLabelValues("foo").(Histogram).Write(m)
|
||||
if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
|
||||
t.Errorf("want %d observations for 'foo' histogram, got %d", want, got)
|
||||
}
|
||||
m.Reset()
|
||||
his.WithLabelValues("bar").(Histogram).Write(m)
|
||||
if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
|
||||
t.Errorf("want %d observations for 'bar' histogram, got %d", want, got)
|
||||
}
|
||||
|
||||
timedFunc()
|
||||
m.Reset()
|
||||
his.WithLabelValues("foo").(Histogram).Write(m)
|
||||
if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
|
||||
t.Errorf("want %d observations for 'foo' histogram, got %d", want, got)
|
||||
}
|
||||
m.Reset()
|
||||
his.WithLabelValues("bar").(Histogram).Write(m)
|
||||
if want, got := uint64(2), m.GetHistogram().GetSampleCount(); want != got {
|
||||
t.Errorf("want %d observations for 'bar' histogram, got %d", want, got)
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue