mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-25 05:34:05 -08:00
Merge branch 'master' into dev-2.0
This commit is contained in:
commit
73b8ff0ddc
|
@ -3,11 +3,9 @@ sudo: false
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.8
|
||||
- 1.8.x
|
||||
|
||||
go_import_path: github.com/prometheus/prometheus
|
||||
|
||||
script:
|
||||
- make check_license style test
|
||||
|
||||
|
||||
|
|
69
CHANGELOG.md
69
CHANGELOG.md
|
@ -1,3 +1,72 @@
|
|||
## 1.6.1 / 2017-04-19
|
||||
|
||||
* [BUGFIX] Don't panic if storage has no FPs even after initial wait
|
||||
|
||||
## 1.6.0 / 2017-04-14
|
||||
|
||||
* [CHANGE] Replaced the remote write implementations for various backends by a
|
||||
generic write interface with example adapter implementation for various
|
||||
backends. Note that both the previous and the current remote write
|
||||
implementations are **experimental**.
|
||||
* [FEATURE] New flag `-storage.local.target-heap-size` to tell Prometheus about
|
||||
the desired heap size. This deprecates the flags
|
||||
`-storage.local.memory-chunks` and `-storage.local.max-chunks-to-persist`,
|
||||
which are kept for backward compatibility.
|
||||
* [FEATURE] Add `check-metrics` to `promtool` to lint metric names.
|
||||
* [FEATURE] Add Joyent Triton discovery.
|
||||
* [FEATURE] `X-Prometheus-Scrape-Timeout-Seconds` header in HTTP scrape
|
||||
requests.
|
||||
* [FEATURE] Remote read interface, including example for InfluxDB. **Experimental.**
|
||||
* [FEATURE] Enable Consul SD to connect via TLS.
|
||||
* [FEATURE] Marathon SD supports multiple ports.
|
||||
* [FEATURE] Marathon SD supports bearer token for authentication.
|
||||
* [FEATURE] Custom timeout for queries.
|
||||
* [FEATURE] Expose `buildQueryUrl` in `graph.js`.
|
||||
* [FEATURE] Add `rickshawGraph` property to the graph object in console
|
||||
templates.
|
||||
* [FEATURE] New metrics exported by Prometheus itself:
|
||||
* Summary `prometheus_engine_query_duration_seconds`
|
||||
* Counter `prometheus_evaluator_iterations_missed_total`
|
||||
* Counter `prometheus_evaluator_iterations_total`
|
||||
* Gauge `prometheus_local_storage_open_head_chunks`
|
||||
* Gauge `prometheus_local_storage_target_heap_size`
|
||||
* [ENHANCEMENT] Reduce shut-down time by interrupting an ongoing checkpoint
|
||||
before starting the final checkpoint.
|
||||
* [ENHANCEMENT] Auto-tweak times between checkpoints to limit time spent in
|
||||
checkpointing to 50%.
|
||||
* [ENHANCEMENT] Improved crash recovery deals better with certain index
|
||||
corruptions.
|
||||
* [ENHANCEMENT] Graphing deals better with constant time series.
|
||||
* [ENHANCEMENT] Retry remote writes on recoverable errors.
|
||||
* [ENHANCEMENT] Evict unused chunk descriptors during crash recovery to limit
|
||||
memory usage.
|
||||
* [ENHANCEMENT] Smoother disk usage during series maintenance.
|
||||
* [ENHANCEMENT] Targets on targets page sorted by instance within a job.
|
||||
* [ENHANCEMENT] Sort labels in federation.
|
||||
* [ENHANCEMENT] Set `GOGC=40` by default, which results in much better memory
|
||||
utilization at the price of slightly higher CPU usage. If `GOGC` is set by
|
||||
the user, it is still honored as usual.
|
||||
* [ENHANCEMENT] Close head chunks after being idle for the duration of the
|
||||
configured staleness delta. This helps to persist and evict head chunk of
|
||||
stale series more quickly.
|
||||
* [ENHANCEMENT] Stricter checking of relabel config.
|
||||
* [ENHANCEMENT] Cache busters for static web content.
|
||||
* [ENHANCEMENT] Send Prometheus-specific user-agent header during scrapes.
|
||||
* [ENHANCEMENT] Improved performance of series retention cut-off.
|
||||
* [ENHANCEMENT] Mitigate impact of non-atomic sample ingestion on
|
||||
`histogram_quantile` by enforcing buckets to be monotonic.
|
||||
* [ENHANCEMENT] Released binaries built with Go 1.8.1.
|
||||
* [BUGFIX] Send `instance=""` with federation if `instance` not set.
|
||||
* [BUGFIX] Update to new `client_golang` to get rid of unwanted quantile
|
||||
metrics in summaries.
|
||||
* [BUGFIX] Introduce several additional guards against data corruption.
|
||||
* [BUGFIX] Mark storage dirty and increment
|
||||
`prometheus_local_storage_persist_errors_total` on all relevant errors.
|
||||
* [BUGFIX] Propagate storage errors as 500 in the HTTP API.
|
||||
* [BUGFIX] Fix int64 overflow in timestamps in the HTTP API.
|
||||
* [BUGFIX] Fix deadlock in Zookeeper SD.
|
||||
* [BUGFIX] Fix fuzzy search problems in the web-UI auto-completion.
|
||||
|
||||
## 1.5.2 / 2017-02-10
|
||||
|
||||
* [BUGFIX] Fix series corruption in a special case of series maintenance where
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Maintainers of this repository with their focus areas:
|
||||
|
||||
* Björn Rabenstein <beorn@soundcloud.com>: Local storage; general code-level issues.
|
||||
* Brian Brazil <brian.brazil@robustperception.io>: Console templates; semantics of PromQL, service discovery, and relabeling.
|
||||
* Fabian Reinartz <fabian.reinartz@coreos.com>: PromQL parsing and evaluation; implementation of retrieval, alert notification, and service discovery.
|
||||
* Julius Volz <julius.volz@gmail.com>: Remote storage integrations; web UI.
|
||||
* Björn Rabenstein <beorn@soundcloud.com> @beorn7: Local storage; general code-level issues.
|
||||
* Brian Brazil <brian.brazil@robustperception.io> @brian-brazil: Console templates; semantics of PromQL, service discovery, and relabeling.
|
||||
* Fabian Reinartz <fabian.reinartz@coreos.com> @fabxc: PromQL parsing and evaluation; implementation of retrieval, alert notification, and service discovery.
|
||||
* Julius Volz <julius.volz@gmail.com> @juliusv: Remote storage integrations; web UI.
|
||||
|
||||
|
|
8
Makefile
8
Makefile
|
@ -37,9 +37,13 @@ check_license:
|
|||
@./scripts/check_license.sh
|
||||
|
||||
# TODO(fabxc): example tests temporarily removed.
|
||||
test:
|
||||
test-short:
|
||||
@echo ">> running short tests"
|
||||
@$(GO) test -short $(shell $(GO) list ./... | grep -v /vendor/ | grep -v examples)
|
||||
@$(GO) test $(shell $(GO) list ./... | grep -v /vendor/ | grep -v examples)
|
||||
|
||||
test:
|
||||
@echo ">> running all tests"
|
||||
@$(GO) test $(pkgs)
|
||||
|
||||
format:
|
||||
@echo ">> formatting code"
|
||||
|
|
|
@ -81,6 +81,7 @@ The Makefile provides several targets:
|
|||
|
||||
* *build*: build the `prometheus` and `promtool` binaries
|
||||
* *test*: run the tests
|
||||
* *test-short*: run the short tests
|
||||
* *format*: format the source code
|
||||
* *vet*: check the source code for common errors
|
||||
* *assets*: rebuild the static assets
|
||||
|
|
|
@ -49,6 +49,7 @@ deployment:
|
|||
owner: prometheus
|
||||
commands:
|
||||
- promu crossbuild tarballs
|
||||
- promu checksum .tarballs
|
||||
- promu release .tarballs
|
||||
- mkdir $CIRCLE_ARTIFACTS/releases/ && cp -a .tarballs/* $CIRCLE_ARTIFACTS/releases/
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/util/cli"
|
||||
"github.com/prometheus/prometheus/util/promlint"
|
||||
)
|
||||
|
||||
// CheckConfigCmd validates configuration files.
|
||||
|
@ -182,6 +183,42 @@ func checkRules(t cli.Term, filename string) (int, error) {
|
|||
return len(rules), nil
|
||||
}
|
||||
|
||||
var checkMetricsUsage = strings.TrimSpace(`
|
||||
usage: promtool check-metrics
|
||||
|
||||
Pass Prometheus metrics over stdin to lint them for consistency and correctness.
|
||||
|
||||
examples:
|
||||
|
||||
$ cat metrics.prom | promtool check-metrics
|
||||
$ curl -s http://localhost:9090/metrics | promtool check-metrics
|
||||
`)
|
||||
|
||||
// CheckMetricsCmd performs a linting pass on input metrics.
|
||||
func CheckMetricsCmd(t cli.Term, args ...string) int {
|
||||
if len(args) != 0 {
|
||||
t.Infof(checkMetricsUsage)
|
||||
return 2
|
||||
}
|
||||
|
||||
l := promlint.New(os.Stdin)
|
||||
problems, err := l.Lint()
|
||||
if err != nil {
|
||||
t.Errorf("error while linting: %v", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
for _, p := range problems {
|
||||
t.Errorf("%s: %s", p.Metric, p.Text)
|
||||
}
|
||||
|
||||
if len(problems) > 0 {
|
||||
return 3
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// VersionCmd prints the binaries version information.
|
||||
func VersionCmd(t cli.Term, _ ...string) int {
|
||||
fmt.Fprintln(os.Stdout, version.Print("promtool"))
|
||||
|
@ -201,6 +238,11 @@ func main() {
|
|||
Run: CheckRulesCmd,
|
||||
})
|
||||
|
||||
app.Register("check-metrics", &cli.Command{
|
||||
Desc: "validate metrics for correctness",
|
||||
Run: CheckMetricsCmd,
|
||||
})
|
||||
|
||||
app.Register("version", &cli.Command{
|
||||
Desc: "print the version of this binary",
|
||||
Run: VersionCmd,
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
{{/* vim: set ft=html: */}}
|
||||
{{/* Load Prometheus console library JS/CSS. Should go in <head> */}}
|
||||
{{ define "prom_console_head" }}
|
||||
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/rickshaw/rickshaw.min.css?v={{ buildVersion }}">
|
||||
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/bootstrap-3.3.1/css/bootstrap.min.css?v={{ buildVersion }}">
|
||||
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/css/prom_console.css?v={{ buildVersion }}">
|
||||
<script src="{{ pathPrefix }}/static/vendor/rickshaw/vendor/d3.v3.js?v={{ buildVersion }}"></script>
|
||||
<script src="{{ pathPrefix }}/static/vendor/rickshaw/vendor/d3.layout.min.js?v={{ buildVersion }}"></script>
|
||||
<script src="{{ pathPrefix }}/static/vendor/rickshaw/rickshaw.min.js?v={{ buildVersion }}"></script>
|
||||
<script src="{{ pathPrefix }}/static/vendor/js/jquery.min.js?v={{ buildVersion }}"></script>
|
||||
<script src="{{ pathPrefix }}/static/vendor/bootstrap-3.3.1/js/bootstrap.min.js?v={{ buildVersion }}"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/rickshaw/rickshaw.min.css">
|
||||
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/bootstrap-3.3.1/css/bootstrap.min.css">
|
||||
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/css/prom_console.css">
|
||||
<script src="{{ pathPrefix }}/static/vendor/rickshaw/vendor/d3.v3.js"></script>
|
||||
<script src="{{ pathPrefix }}/static/vendor/rickshaw/vendor/d3.layout.min.js"></script>
|
||||
<script src="{{ pathPrefix }}/static/vendor/rickshaw/rickshaw.min.js"></script>
|
||||
<script src="{{ pathPrefix }}/static/vendor/js/jquery.min.js"></script>
|
||||
<script src="{{ pathPrefix }}/static/vendor/bootstrap-3.3.1/js/bootstrap.min.js"></script>
|
||||
|
||||
<script>
|
||||
var PATH_PREFIX = "{{ pathPrefix }}";
|
||||
var BUILD_VERSION = "{{ buildVersion }}";
|
||||
</script>
|
||||
<script src="{{ pathPrefix }}/static/js/prom_console.js?v={{ buildVersion }}"></script>
|
||||
<script src="{{ pathPrefix }}/static/js/prom_console.js"></script>
|
||||
{{ end }}
|
||||
|
||||
{{/* Top of all pages. */}}
|
||||
|
|
|
@ -217,11 +217,6 @@ func (ts *TargetSet) UpdateProviders(p map[string]TargetProvider) {
|
|||
}
|
||||
|
||||
func (ts *TargetSet) updateProviders(ctx context.Context, providers map[string]TargetProvider) {
|
||||
// Lock for the entire time. This may mean up to 5 seconds until the full initial set
|
||||
// is retrieved and applied.
|
||||
// We could release earlier with some tweaks, but this is easier to reason about.
|
||||
ts.mtx.Lock()
|
||||
defer ts.mtx.Unlock()
|
||||
|
||||
// Stop all previous target providers of the target set.
|
||||
if ts.cancelProviders != nil {
|
||||
|
@ -233,7 +228,9 @@ func (ts *TargetSet) updateProviders(ctx context.Context, providers map[string]T
|
|||
// (Re-)create a fresh tgroups map to not keep stale targets around. We
|
||||
// will retrieve all targets below anyway, so cleaning up everything is
|
||||
// safe and doesn't inflict any additional cost.
|
||||
ts.mtx.Lock()
|
||||
ts.tgroups = map[string]*config.TargetGroup{}
|
||||
ts.mtx.Unlock()
|
||||
|
||||
for name, prov := range providers {
|
||||
wg.Add(1)
|
||||
|
@ -292,9 +289,6 @@ func (ts *TargetSet) updateProviders(ctx context.Context, providers map[string]T
|
|||
|
||||
// update handles a target group update from a target provider identified by the name.
|
||||
func (ts *TargetSet) update(name string, tgroup *config.TargetGroup) {
|
||||
ts.mtx.Lock()
|
||||
defer ts.mtx.Unlock()
|
||||
|
||||
ts.setTargetGroup(name, tgroup)
|
||||
|
||||
select {
|
||||
|
@ -304,6 +298,9 @@ func (ts *TargetSet) update(name string, tgroup *config.TargetGroup) {
|
|||
}
|
||||
|
||||
func (ts *TargetSet) setTargetGroup(name string, tg *config.TargetGroup) {
|
||||
ts.mtx.Lock()
|
||||
defer ts.mtx.Unlock()
|
||||
|
||||
if tg == nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ const (
|
|||
tritonLabel = model.MetaLabelPrefix + "triton_"
|
||||
tritonLabelMachineId = tritonLabel + "machine_id"
|
||||
tritonLabelMachineAlias = tritonLabel + "machine_alias"
|
||||
tritonLabelMachineBrand = tritonLabel + "machine_brand"
|
||||
tritonLabelMachineImage = tritonLabel + "machine_image"
|
||||
tritonLabelServerId = tritonLabel + "server_id"
|
||||
namespace = "prometheus"
|
||||
|
@ -59,6 +60,7 @@ type DiscoveryResponse struct {
|
|||
Containers []struct {
|
||||
ServerUUID string `json:"server_uuid"`
|
||||
VMAlias string `json:"vm_alias"`
|
||||
VMBrand string `json:"vm_brand"`
|
||||
VMImageUUID string `json:"vm_image_uuid"`
|
||||
VMUUID string `json:"vm_uuid"`
|
||||
} `json:"containers"`
|
||||
|
@ -157,6 +159,7 @@ func (d *Discovery) refresh() (tg *config.TargetGroup, err error) {
|
|||
labels := model.LabelSet{
|
||||
tritonLabelMachineId: model.LabelValue(container.VMUUID),
|
||||
tritonLabelMachineAlias: model.LabelValue(container.VMAlias),
|
||||
tritonLabelMachineBrand: model.LabelValue(container.VMBrand),
|
||||
tritonLabelMachineImage: model.LabelValue(container.VMImageUUID),
|
||||
tritonLabelServerId: model.LabelValue(container.ServerUUID),
|
||||
}
|
||||
|
|
|
@ -111,12 +111,14 @@ func TestTritonSDRefreshMultipleTargets(t *testing.T) {
|
|||
{
|
||||
"server_uuid":"44454c4c-5000-104d-8037-b7c04f5a5131",
|
||||
"vm_alias":"server01",
|
||||
"vm_brand":"lx",
|
||||
"vm_image_uuid":"7b27a514-89d7-11e6-bee6-3f96f367bee7",
|
||||
"vm_uuid":"ad466fbf-46a2-4027-9b64-8d3cdb7e9072"
|
||||
},
|
||||
{
|
||||
"server_uuid":"a5894692-bd32-4ca1-908a-e2dda3c3a5e6",
|
||||
"vm_alias":"server02",
|
||||
"vm_brand":"kvm",
|
||||
"vm_image_uuid":"a5894692-bd32-4ca1-908a-e2dda3c3a5e6",
|
||||
"vm_uuid":"7b27a514-89d7-11e6-bee6-3f96f367bee7"
|
||||
}]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
## Generic Remote Storage Example
|
||||
## Remote Write Adapter Example
|
||||
|
||||
This is a simple example of how to write a server to
|
||||
receive samples from the remote storage output.
|
||||
|
@ -7,7 +7,7 @@ To use it:
|
|||
|
||||
```
|
||||
go build
|
||||
./example_receiver
|
||||
./example_write_adapter
|
||||
```
|
||||
|
||||
...and then add the following to your `prometheus.yml`:
|
|
@ -0,0 +1,55 @@
|
|||
# Remote storage adapter
|
||||
|
||||
This is a write adapter that receives samples via Prometheus's remote write
|
||||
protocol and stores them in Graphite, InfluxDB, or OpenTSDB. It is meant as a
|
||||
replacement for the built-in specific remote storage implementations that have
|
||||
been removed from Prometheus.
|
||||
|
||||
For InfluxDB, this binary is also a read adapter that supports reading back
|
||||
data through Prometheus via Prometheus's remote read protocol.
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
go build
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Graphite example:
|
||||
|
||||
```
|
||||
./remote_storage_adapter -graphite-address=localhost:8080
|
||||
```
|
||||
|
||||
OpenTSDB example:
|
||||
|
||||
```
|
||||
./remote_storage_adapter -opentsdb-url=http://localhost:8081/
|
||||
```
|
||||
|
||||
InfluxDB example:
|
||||
|
||||
```
|
||||
./remote_storage_adapter -influxdb-url=http://localhost:8086/ -influxdb.database=prometheus -influxdb.retention-policy=autogen
|
||||
```
|
||||
|
||||
To show all flags:
|
||||
|
||||
```
|
||||
./remote_storage_adapter -h
|
||||
```
|
||||
|
||||
## Configuring Prometheus
|
||||
|
||||
To configure Prometheus to send samples to this binary, add the following to your `prometheus.yml`:
|
||||
|
||||
```yaml
|
||||
# Remote write configuration (for Graphite, OpenTSDB, or InfluxDB).
|
||||
remote_write:
|
||||
- url: "http://localhost:9201/write"
|
||||
|
||||
# Remote read configuration (for InfluxDB only at the moment).
|
||||
remote_read:
|
||||
- url: "http://localhost:9201/read"
|
||||
```
|
|
@ -124,10 +124,12 @@ func (c *Client) Read(req *remote.ReadRequest) (*remote.ReadResponse, error) {
|
|||
}
|
||||
|
||||
resp := remote.ReadResponse{
|
||||
Timeseries: make([]*remote.TimeSeries, 0, len(labelsToSeries)),
|
||||
Results: []*remote.QueryResult{
|
||||
{Timeseries: make([]*remote.TimeSeries, 0, len(labelsToSeries))},
|
||||
},
|
||||
}
|
||||
for _, ts := range labelsToSeries {
|
||||
resp.Timeseries = append(resp.Timeseries, ts)
|
||||
resp.Results[0].Timeseries = append(resp.Results[0].Timeseries, ts)
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
|
@ -33,9 +33,9 @@ import (
|
|||
|
||||
influx "github.com/influxdata/influxdb/client/v2"
|
||||
|
||||
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_bridge/graphite"
|
||||
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_bridge/influxdb"
|
||||
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_bridge/opentsdb"
|
||||
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/graphite"
|
||||
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/influxdb"
|
||||
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_adapter/opentsdb"
|
||||
"github.com/prometheus/prometheus/storage/remote"
|
||||
)
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
# Remote storage bridge
|
||||
|
||||
This is a bridge that receives samples via Prometheus's remote write
|
||||
protocol and stores them in Graphite, InfluxDB, or OpenTSDB. It is meant
|
||||
as a replacement for the built-in specific remote storage implementations
|
||||
that have been removed from Prometheus.
|
||||
|
||||
For InfluxDB, this bridge also supports reading back data through
|
||||
Prometheus via Prometheus's remote read protocol.
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
go build
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Graphite example:
|
||||
|
||||
```
|
||||
./remote_storage_bridge -graphite-address=localhost:8080
|
||||
```
|
||||
|
||||
OpenTSDB example:
|
||||
|
||||
```
|
||||
./remote_storage_bridge -opentsdb-url=http://localhost:8081/
|
||||
```
|
||||
|
||||
InfluxDB example:
|
||||
|
||||
```
|
||||
./remote_storage_bridge -influxdb-url=http://localhost:8086/ -influxdb.database=prometheus -influxdb.retention-policy=autogen
|
||||
```
|
||||
|
||||
To show all flags:
|
||||
|
||||
```
|
||||
./remote_storage_bridge -h
|
||||
```
|
||||
|
||||
## Configuring Prometheus
|
||||
|
||||
To configure Prometheus to send samples to this bridge, add the following to your `prometheus.yml`:
|
||||
|
||||
```yaml
|
||||
# Remote write configuration (for Graphite, OpenTSDB, or InfluxDB).
|
||||
remote_write:
|
||||
- url: "http://localhost:9201/write"
|
||||
|
||||
# Remote read configuration (for InfluxDB only at the moment).
|
||||
remote_read:
|
||||
- url: "http://localhost:9201/read"
|
||||
```
|
|
@ -369,13 +369,13 @@ func (n *Notifier) setMore() {
|
|||
}
|
||||
}
|
||||
|
||||
// Alertmanagers returns a list Alertmanager URLs.
|
||||
func (n *Notifier) Alertmanagers() []string {
|
||||
// Alertmanagers returns a slice of Alertmanager URLs.
|
||||
func (n *Notifier) Alertmanagers() []*url.URL {
|
||||
n.mtx.RLock()
|
||||
amSets := n.alertmanagers
|
||||
n.mtx.RUnlock()
|
||||
|
||||
var res []string
|
||||
var res []*url.URL
|
||||
|
||||
for _, ams := range amSets {
|
||||
ams.mtx.RLock()
|
||||
|
@ -417,7 +417,7 @@ func (n *Notifier) sendAll(alerts ...*Alert) bool {
|
|||
defer cancel()
|
||||
|
||||
go func(am alertmanager) {
|
||||
u := am.url()
|
||||
u := am.url().String()
|
||||
|
||||
if err := n.sendOne(ctx, ams.client, u, b); err != nil {
|
||||
log.With("alertmanager", u).With("count", len(alerts)).Errorf("Error sending alerts: %s", err)
|
||||
|
@ -465,20 +465,19 @@ func (n *Notifier) Stop() {
|
|||
|
||||
// alertmanager holds Alertmanager endpoint information.
|
||||
type alertmanager interface {
|
||||
url() string
|
||||
url() *url.URL
|
||||
}
|
||||
|
||||
type alertmanagerLabels struct{ labels.Labels }
|
||||
|
||||
const pathLabel = "__alerts_path__"
|
||||
|
||||
func (a alertmanagerLabels) url() string {
|
||||
u := &url.URL{
|
||||
func (a alertmanagerLabels) url() *url.URL {
|
||||
return &url.URL{
|
||||
Scheme: a.Get(model.SchemeLabel),
|
||||
Host: a.Get(model.AddressLabel),
|
||||
Path: a.Get(pathLabel),
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// alertmanagerSet contains a set of Alertmanagers discovered via a group of service
|
||||
|
@ -529,7 +528,7 @@ func (s *alertmanagerSet) Sync(tgs []*config.TargetGroup) {
|
|||
seen := map[string]struct{}{}
|
||||
|
||||
for _, am := range all {
|
||||
us := am.url()
|
||||
us := am.url().String()
|
||||
if _, ok := seen[us]; ok {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -388,6 +389,10 @@ type alertmanagerMock struct {
|
|||
urlf func() string
|
||||
}
|
||||
|
||||
func (a alertmanagerMock) url() string {
|
||||
return a.urlf()
|
||||
func (a alertmanagerMock) url() *url.URL {
|
||||
u, err := url.Parse(a.urlf())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
|
|
@ -113,6 +113,9 @@ type (
|
|||
ErrQueryTimeout string
|
||||
// ErrQueryCanceled is returned if a query was canceled during processing.
|
||||
ErrQueryCanceled string
|
||||
// ErrStorage is returned if an error was encountered in the storage layer
|
||||
// during query handling.
|
||||
ErrStorage error
|
||||
)
|
||||
|
||||
func (e ErrQueryTimeout) Error() string { return fmt.Sprintf("query timed out in %s", string(e)) }
|
||||
|
|
|
@ -83,6 +83,8 @@ func bucketQuantile(q float64, buckets buckets) float64 {
|
|||
return math.NaN()
|
||||
}
|
||||
|
||||
ensureMonotonic(buckets)
|
||||
|
||||
rank := q * buckets[len(buckets)-1].count
|
||||
b := sort.Search(len(buckets)-1, func(i int) bool { return buckets[i].count >= rank })
|
||||
|
||||
|
@ -105,7 +107,52 @@ func bucketQuantile(q float64, buckets buckets) float64 {
|
|||
return bucketStart + (bucketEnd-bucketStart)*float64(rank/count)
|
||||
}
|
||||
|
||||
// qauntile calculates the given quantile of a Vector of samples.
|
||||
// The assumption that bucket counts increase monotonically with increasing
|
||||
// upperBound may be violated during:
|
||||
//
|
||||
// * Recording rule evaluation of histogram_quantile, especially when rate()
|
||||
// has been applied to the underlying bucket timeseries.
|
||||
// * Evaluation of histogram_quantile computed over federated bucket
|
||||
// timeseries, especially when rate() has been applied.
|
||||
//
|
||||
// This is because scraped data is not made available to rule evaluation or
|
||||
// federation atomically, so some buckets are computed with data from the
|
||||
// most recent scrapes, but the other buckets are missing data from the most
|
||||
// recent scrape.
|
||||
//
|
||||
// Monotonicity is usually guaranteed because if a bucket with upper bound
|
||||
// u1 has count c1, then any bucket with a higher upper bound u > u1 must
|
||||
// have counted all c1 observations and perhaps more, so that c >= c1.
|
||||
//
|
||||
// Randomly interspersed partial sampling breaks that guarantee, and rate()
|
||||
// exacerbates it. Specifically, suppose bucket le=1000 has a count of 10 from
|
||||
// 4 samples but the bucket with le=2000 has a count of 7 from 3 samples. The
|
||||
// monotonicity is broken. It is exacerbated by rate() because under normal
|
||||
// operation, cumulative counting of buckets will cause the bucket counts to
|
||||
// diverge such that small differences from missing samples are not a problem.
|
||||
// rate() removes this divergence.)
|
||||
//
|
||||
// bucketQuantile depends on that monotonicity to do a binary search for the
|
||||
// bucket with the φ-quantile count, so breaking the monotonicity
|
||||
// guarantee causes bucketQuantile() to return undefined (nonsense) results.
|
||||
//
|
||||
// As a somewhat hacky solution until ingestion is atomic per scrape, we
|
||||
// calculate the "envelope" of the histogram buckets, essentially removing
|
||||
// any decreases in the count between successive buckets.
|
||||
|
||||
func ensureMonotonic(buckets buckets) {
|
||||
max := buckets[0].count
|
||||
for i := range buckets[1:] {
|
||||
switch {
|
||||
case buckets[i].count > max:
|
||||
max = buckets[i].count
|
||||
case buckets[i].count < max:
|
||||
buckets[i].count = max
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// qauntile calculates the given quantile of a vector of samples.
|
||||
//
|
||||
// The Vector will be sorted.
|
||||
// If 'values' has zero elements, NaN is returned.
|
||||
|
|
18
promql/testdata/histograms.test
vendored
18
promql/testdata/histograms.test
vendored
|
@ -139,3 +139,21 @@ eval instant at 50m histogram_quantile(0.5, rate(request_duration_seconds_bucket
|
|||
{instance="ins2", job="job1"} 0.13333333333333333
|
||||
{instance="ins1", job="job2"} 0.1
|
||||
{instance="ins2", job="job2"} 0.11666666666666667
|
||||
|
||||
# A histogram with nonmonotonic bucket counts. This may happen when recording
|
||||
# rule evaluation or federation races scrape ingestion, causing some buckets
|
||||
# counts to be derived from fewer samples. The wrong answer we want to avoid
|
||||
# is for histogram_quantile(0.99, nonmonotonic_bucket) to return ~1000 instead
|
||||
# of 1.
|
||||
|
||||
load 5m
|
||||
nonmonotonic_bucket{le="0.1"} 0+1x10
|
||||
nonmonotonic_bucket{le="1"} 0+9x10
|
||||
nonmonotonic_bucket{le="10"} 0+8x10
|
||||
nonmonotonic_bucket{le="100"} 0+8x10
|
||||
nonmonotonic_bucket{le="1000"} 0+9x10
|
||||
nonmonotonic_bucket{le="+Inf"} 0+9x10
|
||||
|
||||
# Nonmonotonic buckets
|
||||
eval instant at 50m histogram_quantile(0.99, nonmonotonic_bucket)
|
||||
{} 0.989875
|
||||
|
|
|
@ -184,7 +184,7 @@ func (sp *scrapePool) reload(cfg *config.ScrapeConfig) {
|
|||
for fp, oldLoop := range sp.loops {
|
||||
var (
|
||||
t = sp.targets[fp]
|
||||
s = &targetScraper{Target: t, client: sp.client}
|
||||
s = &targetScraper{Target: t, client: sp.client, timeout: timeout}
|
||||
newLoop = sp.newLoop(sp.ctx, s,
|
||||
func() storage.Appender {
|
||||
return sp.sampleAppender(t)
|
||||
|
@ -253,7 +253,7 @@ func (sp *scrapePool) sync(targets []*Target) {
|
|||
uniqueTargets[hash] = struct{}{}
|
||||
|
||||
if _, ok := sp.targets[hash]; !ok {
|
||||
s := &targetScraper{Target: t, client: sp.client}
|
||||
s := &targetScraper{Target: t, client: sp.client, timeout: timeout}
|
||||
l := sp.newLoop(sp.ctx, s,
|
||||
func() storage.Appender {
|
||||
return sp.sampleAppender(t)
|
||||
|
@ -354,8 +354,9 @@ type scraper interface {
|
|||
type targetScraper struct {
|
||||
*Target
|
||||
|
||||
client *http.Client
|
||||
req *http.Request
|
||||
client *http.Client
|
||||
req *http.Request
|
||||
timeout time.Duration
|
||||
|
||||
gzipr *gzip.Reader
|
||||
buf *bufio.Reader
|
||||
|
@ -372,13 +373,13 @@ func (s *targetScraper) scrape(ctx context.Context, w io.Writer) error {
|
|||
return err
|
||||
}
|
||||
// Disable accept header to always negotiate for text format.
|
||||
// req.Header.Add("Accept", acceptHeader)
|
||||
req.Header.Add("Accept", acceptHeader)
|
||||
req.Header.Add("Accept-Encoding", "gzip")
|
||||
req.Header.Set("User-Agent", userAgentHeader)
|
||||
req.Header.Set("X-Prometheus-Scrape-Timeout-Seconds", fmt.Sprintf("%f", s.timeout.Seconds()))
|
||||
|
||||
s.req = req
|
||||
}
|
||||
|
||||
resp, err := ctxhttp.Do(ctx, s.client, s.req)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -435,8 +435,20 @@ func TestScrapeLoopRun(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTargetScraperScrapeOK(t *testing.T) {
|
||||
const (
|
||||
configTimeout = 1500 * time.Millisecond
|
||||
expectedTimeout = "1.500000"
|
||||
)
|
||||
|
||||
server := httptest.NewServer(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
timeout := r.Header.Get("X-Prometheus-Scrape-Timeout-Seconds")
|
||||
if timeout != expectedTimeout {
|
||||
t.Errorf("Scrape timeout did not match expected timeout")
|
||||
t.Errorf("Expected: %v", expectedTimeout)
|
||||
t.Fatalf("Got: %v", timeout)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", `text/plain; version=0.0.4`)
|
||||
w.Write([]byte("metric_a 1\nmetric_b 2\n"))
|
||||
}),
|
||||
|
@ -455,7 +467,8 @@ func TestTargetScraperScrapeOK(t *testing.T) {
|
|||
model.AddressLabel, serverURL.Host,
|
||||
),
|
||||
},
|
||||
client: http.DefaultClient,
|
||||
client: http.DefaultClient,
|
||||
timeout: configTimeout,
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
|
||||
|
|
268
util/promlint/promlint.go
Normal file
268
util/promlint/promlint.go
Normal file
|
@ -0,0 +1,268 @@
|
|||
// 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 promlint provides a linter for Prometheus metrics.
|
||||
package promlint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/prometheus/common/expfmt"
|
||||
)
|
||||
|
||||
// A Linter is a Prometheus metrics linter. It identifies issues with metric
|
||||
// names, types, and metadata, and reports them to the caller.
|
||||
type Linter struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
// A Problem is an issue detected by a Linter.
|
||||
type Problem struct {
|
||||
// The name of the metric indicated by this Problem.
|
||||
Metric string
|
||||
|
||||
// A description of the issue for this Problem.
|
||||
Text string
|
||||
}
|
||||
|
||||
// problems is a slice of Problems with a helper method to easily append
|
||||
// additional Problems to the slice.
|
||||
type problems []Problem
|
||||
|
||||
// Add appends a new Problem to the slice for the specified metric, with
|
||||
// the specified issue text.
|
||||
func (p *problems) Add(mf dto.MetricFamily, text string) {
|
||||
*p = append(*p, Problem{
|
||||
Metric: mf.GetName(),
|
||||
Text: text,
|
||||
})
|
||||
}
|
||||
|
||||
// New creates a new Linter that reads an input stream of Prometheus metrics.
|
||||
// Only the text exposition format is supported.
|
||||
func New(r io.Reader) *Linter {
|
||||
return &Linter{
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
// Lint performs a linting pass, returning a slice of Problems indicating any
|
||||
// issues found in the metrics stream. The slice is sorted by metric name
|
||||
// and issue description.
|
||||
func (l *Linter) Lint() ([]Problem, error) {
|
||||
// TODO(mdlayher): support for protobuf exposition format?
|
||||
d := expfmt.NewDecoder(l.r, expfmt.FmtText)
|
||||
|
||||
var problems []Problem
|
||||
|
||||
var mf dto.MetricFamily
|
||||
for {
|
||||
if err := d.Decode(&mf); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
problems = append(problems, lint(mf)...)
|
||||
}
|
||||
|
||||
// Ensure deterministic output.
|
||||
sort.SliceStable(problems, func(i, j int) bool {
|
||||
if problems[i].Metric < problems[j].Metric {
|
||||
return true
|
||||
}
|
||||
|
||||
return problems[i].Text < problems[j].Text
|
||||
})
|
||||
|
||||
return problems, nil
|
||||
}
|
||||
|
||||
// lint is the entry point for linting a single metric.
|
||||
func lint(mf dto.MetricFamily) []Problem {
|
||||
fns := []func(mf dto.MetricFamily) []Problem{
|
||||
lintHelp,
|
||||
lintMetricUnits,
|
||||
lintCounter,
|
||||
lintHistogramSummaryReserved,
|
||||
}
|
||||
|
||||
var problems []Problem
|
||||
for _, fn := range fns {
|
||||
problems = append(problems, fn(mf)...)
|
||||
}
|
||||
|
||||
// TODO(mdlayher): lint rules for specific metrics types.
|
||||
return problems
|
||||
}
|
||||
|
||||
// lintHelp detects issues related to the help text for a metric.
|
||||
func lintHelp(mf dto.MetricFamily) []Problem {
|
||||
var problems problems
|
||||
|
||||
// Expect all metrics to have help text available.
|
||||
if mf.Help == nil {
|
||||
problems.Add(mf, "no help text")
|
||||
}
|
||||
|
||||
return problems
|
||||
}
|
||||
|
||||
// lintMetricUnits detects issues with metric unit names.
|
||||
func lintMetricUnits(mf dto.MetricFamily) []Problem {
|
||||
var problems problems
|
||||
|
||||
unit, base, ok := metricUnits(*mf.Name)
|
||||
if !ok {
|
||||
// No known units detected.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unit is already a base unit.
|
||||
if unit == base {
|
||||
return nil
|
||||
}
|
||||
|
||||
problems.Add(mf, fmt.Sprintf("use base unit %q instead of %q", base, unit))
|
||||
|
||||
return problems
|
||||
}
|
||||
|
||||
// lintCounter detects issues specific to counters, as well as patterns that should
|
||||
// only be used with counters.
|
||||
func lintCounter(mf dto.MetricFamily) []Problem {
|
||||
var problems problems
|
||||
|
||||
isCounter := mf.GetType() == dto.MetricType_COUNTER
|
||||
isUntyped := mf.GetType() == dto.MetricType_UNTYPED
|
||||
hasTotalSuffix := strings.HasSuffix(mf.GetName(), "_total")
|
||||
|
||||
switch {
|
||||
case isCounter && !hasTotalSuffix:
|
||||
problems.Add(mf, `counter metrics should have "_total" suffix`)
|
||||
case !isUntyped && !isCounter && hasTotalSuffix:
|
||||
problems.Add(mf, `non-counter metrics should not have "_total" suffix`)
|
||||
}
|
||||
|
||||
return problems
|
||||
}
|
||||
|
||||
// lintHistogramSummaryReserved detects when other types of metrics use names or labels
|
||||
// reserved for use by histograms and/or summaries.
|
||||
func lintHistogramSummaryReserved(mf dto.MetricFamily) []Problem {
|
||||
// These rules do not apply to untyped metrics.
|
||||
t := mf.GetType()
|
||||
if t == dto.MetricType_UNTYPED {
|
||||
return nil
|
||||
}
|
||||
|
||||
var problems problems
|
||||
|
||||
isHistogram := t == dto.MetricType_HISTOGRAM
|
||||
isSummary := t == dto.MetricType_SUMMARY
|
||||
|
||||
n := mf.GetName()
|
||||
|
||||
if !isHistogram && strings.HasSuffix(n, "_bucket") {
|
||||
problems.Add(mf, `non-histogram metrics should not have "_bucket" suffix`)
|
||||
}
|
||||
if !isHistogram && !isSummary && strings.HasSuffix(n, "_count") {
|
||||
problems.Add(mf, `non-histogram and non-summary metrics should not have "_count" suffix`)
|
||||
}
|
||||
if !isHistogram && !isSummary && strings.HasSuffix(n, "_sum") {
|
||||
problems.Add(mf, `non-histogram and non-summary metrics should not have "_sum" suffix`)
|
||||
}
|
||||
|
||||
for _, m := range mf.GetMetric() {
|
||||
for _, l := range m.GetLabel() {
|
||||
ln := l.GetName()
|
||||
|
||||
if !isHistogram && ln == "le" {
|
||||
problems.Add(mf, `non-histogram metrics should not have "le" label`)
|
||||
}
|
||||
if !isSummary && ln == "quantile" {
|
||||
problems.Add(mf, `non-summary metrics should not have "quantile" label`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return problems
|
||||
}
|
||||
|
||||
// metricUnits attempts to detect known unit types used as part of a metric name,
|
||||
// e.g. "foo_bytes_total" or "bar_baz_milligrams".
|
||||
func metricUnits(m string) (unit string, base string, ok bool) {
|
||||
ss := strings.Split(m, "_")
|
||||
|
||||
for _, u := range baseUnits {
|
||||
// Also check for "no prefix".
|
||||
for _, p := range append(unitPrefixes, "") {
|
||||
for _, s := range ss {
|
||||
// Attempt to explicitly match a known unit with a known prefix,
|
||||
// as some words may look like "units" when matching suffix.
|
||||
//
|
||||
// As an example, "thermometers" should not match "meters", but
|
||||
// "kilometers" should.
|
||||
if s == p+u {
|
||||
return p + u, u, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
// Units and their possible prefixes recognized by this library. More can be
|
||||
// added over time as needed.
|
||||
var (
|
||||
baseUnits = []string{
|
||||
"amperes",
|
||||
"bytes",
|
||||
"candela",
|
||||
"grams",
|
||||
"kelvin", // Both plural and non-plural form allowed.
|
||||
"kelvins",
|
||||
"meters", // Both American and international spelling permitted.
|
||||
"metres",
|
||||
"moles",
|
||||
"seconds",
|
||||
}
|
||||
|
||||
unitPrefixes = []string{
|
||||
"pico",
|
||||
"nano",
|
||||
"micro",
|
||||
"milli",
|
||||
"centi",
|
||||
"deci",
|
||||
"deca",
|
||||
"hecto",
|
||||
"kilo",
|
||||
"kibi",
|
||||
"mega",
|
||||
"mibi",
|
||||
"giga",
|
||||
"gibi",
|
||||
"tera",
|
||||
"tebi",
|
||||
"peta",
|
||||
"pebi",
|
||||
}
|
||||
)
|
497
util/promlint/promlint_test.go
Normal file
497
util/promlint/promlint_test.go
Normal file
|
@ -0,0 +1,497 @@
|
|||
// 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 promlint_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/prometheus/util/promlint"
|
||||
)
|
||||
|
||||
func TestLintNoHelpText(t *testing.T) {
|
||||
const msg = "no help text"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
problems []promlint.Problem
|
||||
}{
|
||||
{
|
||||
name: "no help",
|
||||
in: `
|
||||
# TYPE go_goroutines gauge
|
||||
go_goroutines 24
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "go_goroutines",
|
||||
Text: msg,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "empty help",
|
||||
in: `
|
||||
# HELP go_goroutines
|
||||
# TYPE go_goroutines gauge
|
||||
go_goroutines 24
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "go_goroutines",
|
||||
Text: msg,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "no help and empty help",
|
||||
in: `
|
||||
# HELP go_goroutines
|
||||
# TYPE go_goroutines gauge
|
||||
go_goroutines 24
|
||||
# TYPE go_threads gauge
|
||||
go_threads 10
|
||||
`,
|
||||
problems: []promlint.Problem{
|
||||
{
|
||||
Metric: "go_goroutines",
|
||||
Text: msg,
|
||||
},
|
||||
{
|
||||
Metric: "go_threads",
|
||||
Text: msg,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK",
|
||||
in: `
|
||||
# HELP go_goroutines Number of goroutines that currently exist.
|
||||
# TYPE go_goroutines gauge
|
||||
go_goroutines 24
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := promlint.New(strings.NewReader(tt.in))
|
||||
|
||||
problems, err := l.Lint()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if want, got := tt.problems, problems; !reflect.DeepEqual(want, got) {
|
||||
t.Fatalf("unexpected problems:\n- want: %v\n- got: %v",
|
||||
want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLintMetricUnits(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
problems []promlint.Problem
|
||||
}{
|
||||
{
|
||||
name: "amperes",
|
||||
in: `
|
||||
# HELP x_milliamperes Test metric.
|
||||
# TYPE x_milliamperes untyped
|
||||
x_milliamperes 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_milliamperes",
|
||||
Text: `use base unit "amperes" instead of "milliamperes"`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "bytes",
|
||||
in: `
|
||||
# HELP x_gigabytes Test metric.
|
||||
# TYPE x_gigabytes untyped
|
||||
x_gigabytes 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_gigabytes",
|
||||
Text: `use base unit "bytes" instead of "gigabytes"`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "candela",
|
||||
in: `
|
||||
# HELP x_kilocandela Test metric.
|
||||
# TYPE x_kilocandela untyped
|
||||
x_kilocandela 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_kilocandela",
|
||||
Text: `use base unit "candela" instead of "kilocandela"`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "grams",
|
||||
in: `
|
||||
# HELP x_kilograms Test metric.
|
||||
# TYPE x_kilograms untyped
|
||||
x_kilograms 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_kilograms",
|
||||
Text: `use base unit "grams" instead of "kilograms"`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "kelvin",
|
||||
in: `
|
||||
# HELP x_nanokelvin Test metric.
|
||||
# TYPE x_nanokelvin untyped
|
||||
x_nanokelvin 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_nanokelvin",
|
||||
Text: `use base unit "kelvin" instead of "nanokelvin"`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "kelvins",
|
||||
in: `
|
||||
# HELP x_nanokelvins Test metric.
|
||||
# TYPE x_nanokelvins untyped
|
||||
x_nanokelvins 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_nanokelvins",
|
||||
Text: `use base unit "kelvins" instead of "nanokelvins"`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "meters",
|
||||
in: `
|
||||
# HELP x_kilometers Test metric.
|
||||
# TYPE x_kilometers untyped
|
||||
x_kilometers 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_kilometers",
|
||||
Text: `use base unit "meters" instead of "kilometers"`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "metres",
|
||||
in: `
|
||||
# HELP x_kilometres Test metric.
|
||||
# TYPE x_kilometres untyped
|
||||
x_kilometres 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_kilometres",
|
||||
Text: `use base unit "metres" instead of "kilometres"`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "moles",
|
||||
in: `
|
||||
# HELP x_picomoles Test metric.
|
||||
# TYPE x_picomoles untyped
|
||||
x_picomoles 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_picomoles",
|
||||
Text: `use base unit "moles" instead of "picomoles"`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "seconds",
|
||||
in: `
|
||||
# HELP x_microseconds Test metric.
|
||||
# TYPE x_microseconds untyped
|
||||
x_microseconds 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_microseconds",
|
||||
Text: `use base unit "seconds" instead of "microseconds"`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "OK",
|
||||
in: `
|
||||
# HELP thermometers_kelvin Test metric with name that looks like "meters".
|
||||
# TYPE thermometers_kelvin untyped
|
||||
thermometers_kelvin 0
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := promlint.New(strings.NewReader(tt.in))
|
||||
|
||||
problems, err := l.Lint()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if want, got := tt.problems, problems; !reflect.DeepEqual(want, got) {
|
||||
t.Fatalf("unexpected problems:\n- want: %v\n- got: %v",
|
||||
want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLintCounter(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
problems []promlint.Problem
|
||||
}{
|
||||
{
|
||||
name: "counter without _total suffix",
|
||||
in: `
|
||||
# HELP x_bytes Test metric.
|
||||
# TYPE x_bytes counter
|
||||
x_bytes 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_bytes",
|
||||
Text: `counter metrics should have "_total" suffix`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "gauge with _total suffix",
|
||||
in: `
|
||||
# HELP x_bytes_total Test metric.
|
||||
# TYPE x_bytes_total gauge
|
||||
x_bytes_total 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_bytes_total",
|
||||
Text: `non-counter metrics should not have "_total" suffix`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "counter with _total suffix",
|
||||
in: `
|
||||
# HELP x_bytes_total Test metric.
|
||||
# TYPE x_bytes_total counter
|
||||
x_bytes_total 10
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "gauge without _total suffix",
|
||||
in: `
|
||||
# HELP x_bytes Test metric.
|
||||
# TYPE x_bytes gauge
|
||||
x_bytes 10
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "untyped with _total suffix",
|
||||
in: `
|
||||
# HELP x_bytes_total Test metric.
|
||||
# TYPE x_bytes_total untyped
|
||||
x_bytes_total 10
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "untyped without _total suffix",
|
||||
in: `
|
||||
# HELP x_bytes Test metric.
|
||||
# TYPE x_bytes untyped
|
||||
x_bytes 10
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := promlint.New(strings.NewReader(tt.in))
|
||||
|
||||
problems, err := l.Lint()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if want, got := tt.problems, problems; !reflect.DeepEqual(want, got) {
|
||||
t.Fatalf("unexpected problems:\n- want: %v\n- got: %v",
|
||||
want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLintHistogramSummaryReserved(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
problems []promlint.Problem
|
||||
}{
|
||||
{
|
||||
name: "gauge with _bucket suffix",
|
||||
in: `
|
||||
# HELP x_bytes_bucket Test metric.
|
||||
# TYPE x_bytes_bucket gauge
|
||||
x_bytes_bucket 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_bytes_bucket",
|
||||
Text: `non-histogram metrics should not have "_bucket" suffix`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "gauge with _count suffix",
|
||||
in: `
|
||||
# HELP x_bytes_count Test metric.
|
||||
# TYPE x_bytes_count gauge
|
||||
x_bytes_count 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_bytes_count",
|
||||
Text: `non-histogram and non-summary metrics should not have "_count" suffix`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "gauge with _sum suffix",
|
||||
in: `
|
||||
# HELP x_bytes_sum Test metric.
|
||||
# TYPE x_bytes_sum gauge
|
||||
x_bytes_sum 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_bytes_sum",
|
||||
Text: `non-histogram and non-summary metrics should not have "_sum" suffix`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "gauge with le label",
|
||||
in: `
|
||||
# HELP x_bytes Test metric.
|
||||
# TYPE x_bytes gauge
|
||||
x_bytes{le="1"} 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_bytes",
|
||||
Text: `non-histogram metrics should not have "le" label`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "gauge with quantile label",
|
||||
in: `
|
||||
# HELP x_bytes Test metric.
|
||||
# TYPE x_bytes gauge
|
||||
x_bytes{quantile="1"} 10
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "x_bytes",
|
||||
Text: `non-summary metrics should not have "quantile" label`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "histogram with quantile label",
|
||||
in: `
|
||||
# HELP tsdb_compaction_duration Duration of compaction runs.
|
||||
# TYPE tsdb_compaction_duration histogram
|
||||
tsdb_compaction_duration_bucket{le="0.005",quantile="0.01"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.01",quantile="0.01"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.025",quantile="0.01"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.05",quantile="0.01"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.1",quantile="0.01"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.25",quantile="0.01"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.5",quantile="0.01"} 57
|
||||
tsdb_compaction_duration_bucket{le="1",quantile="0.01"} 68
|
||||
tsdb_compaction_duration_bucket{le="2.5",quantile="0.01"} 69
|
||||
tsdb_compaction_duration_bucket{le="5",quantile="0.01"} 69
|
||||
tsdb_compaction_duration_bucket{le="10",quantile="0.01"} 69
|
||||
tsdb_compaction_duration_bucket{le="+Inf",quantile="0.01"} 69
|
||||
tsdb_compaction_duration_sum 28.740810936000006
|
||||
tsdb_compaction_duration_count 69
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "tsdb_compaction_duration",
|
||||
Text: `non-summary metrics should not have "quantile" label`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "summary with le label",
|
||||
in: `
|
||||
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
|
||||
# TYPE go_gc_duration_seconds summary
|
||||
go_gc_duration_seconds{quantile="0",le="0.01"} 4.2365e-05
|
||||
go_gc_duration_seconds{quantile="0.25",le="0.01"} 8.1492e-05
|
||||
go_gc_duration_seconds{quantile="0.5",le="0.01"} 0.000100656
|
||||
go_gc_duration_seconds{quantile="0.75",le="0.01"} 0.000113913
|
||||
go_gc_duration_seconds{quantile="1",le="0.01"} 0.021754305
|
||||
go_gc_duration_seconds_sum 1.769429004
|
||||
go_gc_duration_seconds_count 5962
|
||||
`,
|
||||
problems: []promlint.Problem{{
|
||||
Metric: "go_gc_duration_seconds",
|
||||
Text: `non-histogram metrics should not have "le" label`,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "histogram OK",
|
||||
in: `
|
||||
# HELP tsdb_compaction_duration Duration of compaction runs.
|
||||
# TYPE tsdb_compaction_duration histogram
|
||||
tsdb_compaction_duration_bucket{le="0.005"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.01"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.025"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.05"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.1"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.25"} 0
|
||||
tsdb_compaction_duration_bucket{le="0.5"} 57
|
||||
tsdb_compaction_duration_bucket{le="1"} 68
|
||||
tsdb_compaction_duration_bucket{le="2.5"} 69
|
||||
tsdb_compaction_duration_bucket{le="5"} 69
|
||||
tsdb_compaction_duration_bucket{le="10"} 69
|
||||
tsdb_compaction_duration_bucket{le="+Inf"} 69
|
||||
tsdb_compaction_duration_sum 28.740810936000006
|
||||
tsdb_compaction_duration_count 69
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "summary OK",
|
||||
in: `
|
||||
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
|
||||
# TYPE go_gc_duration_seconds summary
|
||||
go_gc_duration_seconds{quantile="0"} 4.2365e-05
|
||||
go_gc_duration_seconds{quantile="0.25"} 8.1492e-05
|
||||
go_gc_duration_seconds{quantile="0.5"} 0.000100656
|
||||
go_gc_duration_seconds{quantile="0.75"} 0.000113913
|
||||
go_gc_duration_seconds{quantile="1"} 0.021754305
|
||||
go_gc_duration_seconds_sum 1.769429004
|
||||
go_gc_duration_seconds_count 5962
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := promlint.New(strings.NewReader(tt.in))
|
||||
|
||||
problems, err := l.Lint()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if want, got := tt.problems, problems; !reflect.DeepEqual(want, got) {
|
||||
t.Fatalf("unexpected problems:\n- want: %v\n- got: %v",
|
||||
want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
12
vendor/github.com/prometheus/client_golang/prometheus/counter.go
generated
vendored
12
vendor/github.com/prometheus/client_golang/prometheus/counter.go
generated
vendored
|
@ -30,16 +30,8 @@ type Counter interface {
|
|||
Metric
|
||||
Collector
|
||||
|
||||
// Set is used to set the Counter to an arbitrary value. It is only used
|
||||
// if you have to transfer a value from an external counter into this
|
||||
// Prometheus metric. Do not use it for regular handling of a
|
||||
// Prometheus counter (as it can be used to break the contract of
|
||||
// monotonically increasing values).
|
||||
//
|
||||
// Deprecated: Use NewConstMetric to create a counter for an external
|
||||
// value. A Counter should never be set.
|
||||
Set(float64)
|
||||
// Inc increments the counter by 1.
|
||||
// Inc increments the counter by 1. Use Add to increment it by arbitrary
|
||||
// non-negative values.
|
||||
Inc()
|
||||
// Add adds the given value to the counter. It panics if the value is <
|
||||
// 0.
|
||||
|
|
13
vendor/github.com/prometheus/client_golang/prometheus/desc.go
generated
vendored
13
vendor/github.com/prometheus/client_golang/prometheus/desc.go
generated
vendored
|
@ -16,20 +16,15 @@ package prometheus
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
var (
|
||||
metricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`)
|
||||
labelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
|
||||
)
|
||||
|
||||
// reservedLabelPrefix is a prefix which is not legal in user-supplied
|
||||
// label names.
|
||||
const reservedLabelPrefix = "__"
|
||||
|
@ -78,7 +73,7 @@ type Desc struct {
|
|||
// Help string. Each Desc with the same fqName must have the same
|
||||
// dimHash.
|
||||
dimHash uint64
|
||||
// err is an error that occured during construction. It is reported on
|
||||
// err is an error that occurred during construction. It is reported on
|
||||
// registration time.
|
||||
err error
|
||||
}
|
||||
|
@ -103,7 +98,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
|
|||
d.err = errors.New("empty help string")
|
||||
return d
|
||||
}
|
||||
if !metricNameRE.MatchString(fqName) {
|
||||
if !model.IsValidMetricName(model.LabelValue(fqName)) {
|
||||
d.err = fmt.Errorf("%q is not a valid metric name", fqName)
|
||||
return d
|
||||
}
|
||||
|
@ -200,6 +195,6 @@ func (d *Desc) String() string {
|
|||
}
|
||||
|
||||
func checkLabelName(l string) bool {
|
||||
return labelNameRE.MatchString(l) &&
|
||||
return model.LabelName(l).IsValid() &&
|
||||
!strings.HasPrefix(l, reservedLabelPrefix)
|
||||
}
|
||||
|
|
64
vendor/github.com/prometheus/client_golang/prometheus/doc.go
generated
vendored
64
vendor/github.com/prometheus/client_golang/prometheus/doc.go
generated
vendored
|
@ -17,7 +17,7 @@
|
|||
// Pushgateway (package push).
|
||||
//
|
||||
// All exported functions and methods are safe to be used concurrently unless
|
||||
//specified otherwise.
|
||||
// specified otherwise.
|
||||
//
|
||||
// A Basic Example
|
||||
//
|
||||
|
@ -59,7 +59,7 @@
|
|||
// // The Handler function provides a default handler to expose metrics
|
||||
// // via an HTTP server. "/metrics" is the usual endpoint for that.
|
||||
// http.Handle("/metrics", promhttp.Handler())
|
||||
// http.ListenAndServe(":8080", nil)
|
||||
// log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
// }
|
||||
//
|
||||
//
|
||||
|
@ -69,7 +69,7 @@
|
|||
// Metrics
|
||||
//
|
||||
// The number of exported identifiers in this package might appear a bit
|
||||
// overwhelming. Hovever, in addition to the basic plumbing shown in the example
|
||||
// overwhelming. However, in addition to the basic plumbing shown in the example
|
||||
// above, you only need to understand the different metric types and their
|
||||
// vector versions for basic usage.
|
||||
//
|
||||
|
@ -95,8 +95,8 @@
|
|||
// SummaryVec, HistogramVec, and UntypedVec are not.
|
||||
//
|
||||
// To create instances of Metrics and their vector versions, you need a suitable
|
||||
// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts,
|
||||
// HistogramOpts, or UntypedOpts.
|
||||
// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts, HistogramOpts, or
|
||||
// UntypedOpts.
|
||||
//
|
||||
// Custom Collectors and constant Metrics
|
||||
//
|
||||
|
@ -114,8 +114,8 @@
|
|||
// Metric instances “on the fly” using NewConstMetric, NewConstHistogram, and
|
||||
// NewConstSummary (and their respective Must… versions). That will happen in
|
||||
// the Collect method. The Describe method has to return separate Desc
|
||||
// instances, representative of the “throw-away” metrics to be created
|
||||
// later. NewDesc comes in handy to create those Desc instances.
|
||||
// instances, representative of the “throw-away” metrics to be created later.
|
||||
// NewDesc comes in handy to create those Desc instances.
|
||||
//
|
||||
// The Collector example illustrates the use case. You can also look at the
|
||||
// source code of the processCollector (mirroring process metrics), the
|
||||
|
@ -129,32 +129,32 @@
|
|||
// Advanced Uses of the Registry
|
||||
//
|
||||
// While MustRegister is the by far most common way of registering a Collector,
|
||||
// sometimes you might want to handle the errors the registration might
|
||||
// cause. As suggested by the name, MustRegister panics if an error occurs. With
|
||||
// the Register function, the error is returned and can be handled.
|
||||
// sometimes you might want to handle the errors the registration might cause.
|
||||
// As suggested by the name, MustRegister panics if an error occurs. With the
|
||||
// Register function, the error is returned and can be handled.
|
||||
//
|
||||
// An error is returned if the registered Collector is incompatible or
|
||||
// inconsistent with already registered metrics. The registry aims for
|
||||
// consistency of the collected metrics according to the Prometheus data
|
||||
// model. Inconsistencies are ideally detected at registration time, not at
|
||||
// collect time. The former will usually be detected at start-up time of a
|
||||
// program, while the latter will only happen at scrape time, possibly not even
|
||||
// on the first scrape if the inconsistency only becomes relevant later. That is
|
||||
// the main reason why a Collector and a Metric have to describe themselves to
|
||||
// the registry.
|
||||
// consistency of the collected metrics according to the Prometheus data model.
|
||||
// Inconsistencies are ideally detected at registration time, not at collect
|
||||
// time. The former will usually be detected at start-up time of a program,
|
||||
// while the latter will only happen at scrape time, possibly not even on the
|
||||
// first scrape if the inconsistency only becomes relevant later. That is the
|
||||
// main reason why a Collector and a Metric have to describe themselves to the
|
||||
// registry.
|
||||
//
|
||||
// So far, everything we did operated on the so-called default registry, as it
|
||||
// can be found in the global DefaultRegistry variable. With NewRegistry, you
|
||||
// can create a custom registry, or you can even implement the Registerer or
|
||||
// Gatherer interfaces yourself. The methods Register and Unregister work in
|
||||
// the same way on a custom registry as the global functions Register and
|
||||
// Unregister on the default registry.
|
||||
// Gatherer interfaces yourself. The methods Register and Unregister work in the
|
||||
// same way on a custom registry as the global functions Register and Unregister
|
||||
// on the default registry.
|
||||
//
|
||||
// There are a number of uses for custom registries: You can use registries
|
||||
// with special properties, see NewPedanticRegistry. You can avoid global state,
|
||||
// as it is imposed by the DefaultRegistry. You can use multiple registries at
|
||||
// the same time to expose different metrics in different ways. You can use
|
||||
// separate registries for testing purposes.
|
||||
// There are a number of uses for custom registries: You can use registries with
|
||||
// special properties, see NewPedanticRegistry. You can avoid global state, as
|
||||
// it is imposed by the DefaultRegistry. You can use multiple registries at the
|
||||
// same time to expose different metrics in different ways. You can use separate
|
||||
// registries for testing purposes.
|
||||
//
|
||||
// Also note that the DefaultRegistry comes registered with a Collector for Go
|
||||
// runtime metrics (via NewGoCollector) and a Collector for process metrics (via
|
||||
|
@ -166,16 +166,20 @@
|
|||
// The Registry implements the Gatherer interface. The caller of the Gather
|
||||
// method can then expose the gathered metrics in some way. Usually, the metrics
|
||||
// are served via HTTP on the /metrics endpoint. That's happening in the example
|
||||
// above. The tools to expose metrics via HTTP are in the promhttp
|
||||
// sub-package. (The top-level functions in the prometheus package are
|
||||
// deprecated.)
|
||||
// above. The tools to expose metrics via HTTP are in the promhttp sub-package.
|
||||
// (The top-level functions in the prometheus package are deprecated.)
|
||||
//
|
||||
// Pushing to the Pushgateway
|
||||
//
|
||||
// Function for pushing to the Pushgateway can be found in the push sub-package.
|
||||
//
|
||||
// Graphite Bridge
|
||||
//
|
||||
// Functions and examples to push metrics from a Gatherer to Graphite can be
|
||||
// found in the graphite sub-package.
|
||||
//
|
||||
// Other Means of Exposition
|
||||
//
|
||||
// More ways of exposing metrics can easily be added. Sending metrics to
|
||||
// Graphite would be an example that will soon be implemented.
|
||||
// More ways of exposing metrics can easily be added by following the approaches
|
||||
// of the existing implementations.
|
||||
package prometheus
|
||||
|
|
13
vendor/github.com/prometheus/client_golang/prometheus/gauge.go
generated
vendored
13
vendor/github.com/prometheus/client_golang/prometheus/gauge.go
generated
vendored
|
@ -27,16 +27,21 @@ type Gauge interface {
|
|||
|
||||
// Set sets the Gauge to an arbitrary value.
|
||||
Set(float64)
|
||||
// Inc increments the Gauge by 1.
|
||||
// Inc increments the Gauge by 1. Use Add to increment it by arbitrary
|
||||
// values.
|
||||
Inc()
|
||||
// Dec decrements the Gauge by 1.
|
||||
// Dec decrements the Gauge by 1. Use Sub to decrement it by arbitrary
|
||||
// values.
|
||||
Dec()
|
||||
// Add adds the given value to the Gauge. (The value can be
|
||||
// negative, resulting in a decrease of the Gauge.)
|
||||
// Add adds the given value to the Gauge. (The value can be negative,
|
||||
// resulting in a decrease of the Gauge.)
|
||||
Add(float64)
|
||||
// Sub subtracts the given value from the Gauge. (The value can be
|
||||
// negative, resulting in an increase of the Gauge.)
|
||||
Sub(float64)
|
||||
|
||||
// SetToCurrentTime sets the Gauge to the current Unix time in seconds.
|
||||
SetToCurrentTime()
|
||||
}
|
||||
|
||||
// GaugeOpts is an alias for Opts. See there for doc comments.
|
||||
|
|
43
vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
generated
vendored
43
vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
generated
vendored
|
@ -8,8 +8,9 @@ import (
|
|||
)
|
||||
|
||||
type goCollector struct {
|
||||
goroutines Gauge
|
||||
gcDesc *Desc
|
||||
goroutinesDesc *Desc
|
||||
threadsDesc *Desc
|
||||
gcDesc *Desc
|
||||
|
||||
// metrics to describe and collect
|
||||
metrics memStatsMetrics
|
||||
|
@ -19,11 +20,14 @@ type goCollector struct {
|
|||
// go process.
|
||||
func NewGoCollector() Collector {
|
||||
return &goCollector{
|
||||
goroutines: NewGauge(GaugeOpts{
|
||||
Namespace: "go",
|
||||
Name: "goroutines",
|
||||
Help: "Number of goroutines that currently exist.",
|
||||
}),
|
||||
goroutinesDesc: NewDesc(
|
||||
"go_goroutines",
|
||||
"Number of goroutines that currently exist.",
|
||||
nil, nil),
|
||||
threadsDesc: NewDesc(
|
||||
"go_threads",
|
||||
"Number of OS threads created",
|
||||
nil, nil),
|
||||
gcDesc: NewDesc(
|
||||
"go_gc_duration_seconds",
|
||||
"A summary of the GC invocation durations.",
|
||||
|
@ -48,7 +52,7 @@ func NewGoCollector() Collector {
|
|||
}, {
|
||||
desc: NewDesc(
|
||||
memstatNamespace("sys_bytes"),
|
||||
"Number of bytes obtained by system. Sum of all system allocations.",
|
||||
"Number of bytes obtained from system.",
|
||||
nil, nil,
|
||||
),
|
||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.Sys) },
|
||||
|
@ -111,12 +115,12 @@ func NewGoCollector() Collector {
|
|||
valType: GaugeValue,
|
||||
}, {
|
||||
desc: NewDesc(
|
||||
memstatNamespace("heap_released_bytes_total"),
|
||||
"Total number of heap bytes released to OS.",
|
||||
memstatNamespace("heap_released_bytes"),
|
||||
"Number of heap bytes released to OS.",
|
||||
nil, nil,
|
||||
),
|
||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapReleased) },
|
||||
valType: CounterValue,
|
||||
valType: GaugeValue,
|
||||
}, {
|
||||
desc: NewDesc(
|
||||
memstatNamespace("heap_objects"),
|
||||
|
@ -213,6 +217,14 @@ func NewGoCollector() Collector {
|
|||
),
|
||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.LastGC) / 1e9 },
|
||||
valType: GaugeValue,
|
||||
}, {
|
||||
desc: NewDesc(
|
||||
memstatNamespace("gc_cpu_fraction"),
|
||||
"The fraction of this program's available CPU time used by the GC since the program started.",
|
||||
nil, nil,
|
||||
),
|
||||
eval: func(ms *runtime.MemStats) float64 { return ms.GCCPUFraction },
|
||||
valType: GaugeValue,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -224,9 +236,9 @@ func memstatNamespace(s string) string {
|
|||
|
||||
// Describe returns all descriptions of the collector.
|
||||
func (c *goCollector) Describe(ch chan<- *Desc) {
|
||||
ch <- c.goroutines.Desc()
|
||||
ch <- c.goroutinesDesc
|
||||
ch <- c.threadsDesc
|
||||
ch <- c.gcDesc
|
||||
|
||||
for _, i := range c.metrics {
|
||||
ch <- i.desc
|
||||
}
|
||||
|
@ -234,8 +246,9 @@ func (c *goCollector) Describe(ch chan<- *Desc) {
|
|||
|
||||
// Collect returns the current state of all metrics of the collector.
|
||||
func (c *goCollector) Collect(ch chan<- Metric) {
|
||||
c.goroutines.Set(float64(runtime.NumGoroutine()))
|
||||
ch <- c.goroutines
|
||||
ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine()))
|
||||
n, _ := runtime.ThreadCreateProfile(nil)
|
||||
ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, float64(n))
|
||||
|
||||
var stats debug.GCStats
|
||||
stats.PauseQuantiles = make([]time.Duration, 5)
|
||||
|
|
96
vendor/github.com/prometheus/client_golang/prometheus/http.go
generated
vendored
96
vendor/github.com/prometheus/client_golang/prometheus/http.go
generated
vendored
|
@ -62,7 +62,7 @@ func giveBuf(buf *bytes.Buffer) {
|
|||
//
|
||||
// Deprecated: Please note the issues described in the doc comment of
|
||||
// InstrumentHandler. You might want to consider using promhttp.Handler instead
|
||||
// (which is non instrumented).
|
||||
// (which is not instrumented).
|
||||
func Handler() http.Handler {
|
||||
return InstrumentHandler("prometheus", UninstrumentedHandler())
|
||||
}
|
||||
|
@ -172,6 +172,9 @@ func nowSeries(t ...time.Time) nower {
|
|||
// httputil.ReverseProxy is a prominent example for a handler
|
||||
// performing such writes.
|
||||
//
|
||||
// - It has additional issues with HTTP/2, cf.
|
||||
// https://github.com/prometheus/client_golang/issues/272.
|
||||
//
|
||||
// Upcoming versions of this package will provide ways of instrumenting HTTP
|
||||
// handlers that are more flexible and have fewer issues. Please prefer direct
|
||||
// instrumentation in the meantime.
|
||||
|
@ -190,6 +193,7 @@ func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWri
|
|||
SummaryOpts{
|
||||
Subsystem: "http",
|
||||
ConstLabels: Labels{"handler": handlerName},
|
||||
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
|
||||
},
|
||||
handlerFunc,
|
||||
)
|
||||
|
@ -245,34 +249,52 @@ func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.Respo
|
|||
},
|
||||
instLabels,
|
||||
)
|
||||
if err := Register(reqCnt); err != nil {
|
||||
if are, ok := err.(AlreadyRegisteredError); ok {
|
||||
reqCnt = are.ExistingCollector.(*CounterVec)
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
opts.Name = "request_duration_microseconds"
|
||||
opts.Help = "The HTTP request latencies in microseconds."
|
||||
reqDur := NewSummary(opts)
|
||||
if err := Register(reqDur); err != nil {
|
||||
if are, ok := err.(AlreadyRegisteredError); ok {
|
||||
reqDur = are.ExistingCollector.(Summary)
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
opts.Name = "request_size_bytes"
|
||||
opts.Help = "The HTTP request sizes in bytes."
|
||||
reqSz := NewSummary(opts)
|
||||
if err := Register(reqSz); err != nil {
|
||||
if are, ok := err.(AlreadyRegisteredError); ok {
|
||||
reqSz = are.ExistingCollector.(Summary)
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
opts.Name = "response_size_bytes"
|
||||
opts.Help = "The HTTP response sizes in bytes."
|
||||
resSz := NewSummary(opts)
|
||||
|
||||
regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec)
|
||||
regReqDur := MustRegisterOrGet(reqDur).(Summary)
|
||||
regReqSz := MustRegisterOrGet(reqSz).(Summary)
|
||||
regResSz := MustRegisterOrGet(resSz).(Summary)
|
||||
if err := Register(resSz); err != nil {
|
||||
if are, ok := err.(AlreadyRegisteredError); ok {
|
||||
resSz = are.ExistingCollector.(Summary)
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now()
|
||||
|
||||
delegate := &responseWriterDelegator{ResponseWriter: w}
|
||||
out := make(chan int)
|
||||
urlLen := 0
|
||||
if r.URL != nil {
|
||||
urlLen = len(r.URL.String())
|
||||
}
|
||||
go computeApproximateRequestSize(r, out, urlLen)
|
||||
out := computeApproximateRequestSize(r)
|
||||
|
||||
_, cn := w.(http.CloseNotifier)
|
||||
_, fl := w.(http.Flusher)
|
||||
|
@ -290,30 +312,44 @@ func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.Respo
|
|||
|
||||
method := sanitizeMethod(r.Method)
|
||||
code := sanitizeCode(delegate.status)
|
||||
regReqCnt.WithLabelValues(method, code).Inc()
|
||||
regReqDur.Observe(elapsed)
|
||||
regResSz.Observe(float64(delegate.written))
|
||||
regReqSz.Observe(float64(<-out))
|
||||
reqCnt.WithLabelValues(method, code).Inc()
|
||||
reqDur.Observe(elapsed)
|
||||
resSz.Observe(float64(delegate.written))
|
||||
reqSz.Observe(float64(<-out))
|
||||
})
|
||||
}
|
||||
|
||||
func computeApproximateRequestSize(r *http.Request, out chan int, s int) {
|
||||
s += len(r.Method)
|
||||
s += len(r.Proto)
|
||||
for name, values := range r.Header {
|
||||
s += len(name)
|
||||
for _, value := range values {
|
||||
s += len(value)
|
||||
func computeApproximateRequestSize(r *http.Request) <-chan int {
|
||||
// Get URL length in current go routine for avoiding a race condition.
|
||||
// HandlerFunc that runs in parallel may modify the URL.
|
||||
s := 0
|
||||
if r.URL != nil {
|
||||
s += len(r.URL.String())
|
||||
}
|
||||
|
||||
out := make(chan int, 1)
|
||||
|
||||
go func() {
|
||||
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)
|
||||
s += len(r.Host)
|
||||
|
||||
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
|
||||
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
|
||||
|
||||
if r.ContentLength != -1 {
|
||||
s += int(r.ContentLength)
|
||||
}
|
||||
out <- s
|
||||
if r.ContentLength != -1 {
|
||||
s += int(r.ContentLength)
|
||||
}
|
||||
out <- s
|
||||
close(out)
|
||||
}()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
type responseWriterDelegator struct {
|
||||
|
|
104
vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
generated
vendored
104
vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
generated
vendored
|
@ -19,10 +19,10 @@ type processCollector struct {
|
|||
pid int
|
||||
collectFn func(chan<- Metric)
|
||||
pidFn func() (int, error)
|
||||
cpuTotal Counter
|
||||
openFDs, maxFDs Gauge
|
||||
vsize, rss Gauge
|
||||
startTime Gauge
|
||||
cpuTotal *Desc
|
||||
openFDs, maxFDs *Desc
|
||||
vsize, rss *Desc
|
||||
startTime *Desc
|
||||
}
|
||||
|
||||
// NewProcessCollector returns a collector which exports the current state of
|
||||
|
@ -44,40 +44,45 @@ func NewProcessCollectorPIDFn(
|
|||
pidFn func() (int, error),
|
||||
namespace string,
|
||||
) Collector {
|
||||
ns := ""
|
||||
if len(namespace) > 0 {
|
||||
ns = namespace + "_"
|
||||
}
|
||||
|
||||
c := processCollector{
|
||||
pidFn: pidFn,
|
||||
collectFn: func(chan<- Metric) {},
|
||||
|
||||
cpuTotal: NewCounter(CounterOpts{
|
||||
Namespace: namespace,
|
||||
Name: "process_cpu_seconds_total",
|
||||
Help: "Total user and system CPU time spent in seconds.",
|
||||
}),
|
||||
openFDs: NewGauge(GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "process_open_fds",
|
||||
Help: "Number of open file descriptors.",
|
||||
}),
|
||||
maxFDs: NewGauge(GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "process_max_fds",
|
||||
Help: "Maximum number of open file descriptors.",
|
||||
}),
|
||||
vsize: NewGauge(GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "process_virtual_memory_bytes",
|
||||
Help: "Virtual memory size in bytes.",
|
||||
}),
|
||||
rss: NewGauge(GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "process_resident_memory_bytes",
|
||||
Help: "Resident memory size in bytes.",
|
||||
}),
|
||||
startTime: NewGauge(GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "process_start_time_seconds",
|
||||
Help: "Start time of the process since unix epoch in seconds.",
|
||||
}),
|
||||
cpuTotal: NewDesc(
|
||||
ns+"process_cpu_seconds_total",
|
||||
"Total user and system CPU time spent in seconds.",
|
||||
nil, nil,
|
||||
),
|
||||
openFDs: NewDesc(
|
||||
ns+"process_open_fds",
|
||||
"Number of open file descriptors.",
|
||||
nil, nil,
|
||||
),
|
||||
maxFDs: NewDesc(
|
||||
ns+"process_max_fds",
|
||||
"Maximum number of open file descriptors.",
|
||||
nil, nil,
|
||||
),
|
||||
vsize: NewDesc(
|
||||
ns+"process_virtual_memory_bytes",
|
||||
"Virtual memory size in bytes.",
|
||||
nil, nil,
|
||||
),
|
||||
rss: NewDesc(
|
||||
ns+"process_resident_memory_bytes",
|
||||
"Resident memory size in bytes.",
|
||||
nil, nil,
|
||||
),
|
||||
startTime: NewDesc(
|
||||
ns+"process_start_time_seconds",
|
||||
"Start time of the process since unix epoch in seconds.",
|
||||
nil, nil,
|
||||
),
|
||||
}
|
||||
|
||||
// Set up process metric collection if supported by the runtime.
|
||||
|
@ -90,12 +95,12 @@ func NewProcessCollectorPIDFn(
|
|||
|
||||
// Describe returns all descriptions of the collector.
|
||||
func (c *processCollector) Describe(ch chan<- *Desc) {
|
||||
ch <- c.cpuTotal.Desc()
|
||||
ch <- c.openFDs.Desc()
|
||||
ch <- c.maxFDs.Desc()
|
||||
ch <- c.vsize.Desc()
|
||||
ch <- c.rss.Desc()
|
||||
ch <- c.startTime.Desc()
|
||||
ch <- c.cpuTotal
|
||||
ch <- c.openFDs
|
||||
ch <- c.maxFDs
|
||||
ch <- c.vsize
|
||||
ch <- c.rss
|
||||
ch <- c.startTime
|
||||
}
|
||||
|
||||
// Collect returns the current state of all metrics of the collector.
|
||||
|
@ -117,26 +122,19 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
|
|||
}
|
||||
|
||||
if stat, err := p.NewStat(); err == nil {
|
||||
c.cpuTotal.Set(stat.CPUTime())
|
||||
ch <- c.cpuTotal
|
||||
c.vsize.Set(float64(stat.VirtualMemory()))
|
||||
ch <- c.vsize
|
||||
c.rss.Set(float64(stat.ResidentMemory()))
|
||||
ch <- c.rss
|
||||
|
||||
ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime())
|
||||
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory()))
|
||||
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory()))
|
||||
if startTime, err := stat.StartTime(); err == nil {
|
||||
c.startTime.Set(startTime)
|
||||
ch <- c.startTime
|
||||
ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime)
|
||||
}
|
||||
}
|
||||
|
||||
if fds, err := p.FileDescriptorsLen(); err == nil {
|
||||
c.openFDs.Set(float64(fds))
|
||||
ch <- c.openFDs
|
||||
ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds))
|
||||
}
|
||||
|
||||
if limits, err := p.NewLimits(); err == nil {
|
||||
c.maxFDs.Set(float64(limits.OpenFiles))
|
||||
ch <- c.maxFDs
|
||||
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles))
|
||||
}
|
||||
}
|
||||
|
|
57
vendor/github.com/prometheus/client_golang/prometheus/registry.go
generated
vendored
57
vendor/github.com/prometheus/client_golang/prometheus/registry.go
generated
vendored
|
@ -152,38 +152,6 @@ func MustRegister(cs ...Collector) {
|
|||
DefaultRegisterer.MustRegister(cs...)
|
||||
}
|
||||
|
||||
// RegisterOrGet registers the provided Collector with the DefaultRegisterer and
|
||||
// returns the Collector, unless an equal Collector was registered before, in
|
||||
// which case that Collector is returned.
|
||||
//
|
||||
// Deprecated: RegisterOrGet is merely a convenience function for the
|
||||
// implementation as described in the documentation for
|
||||
// AlreadyRegisteredError. As the use case is relatively rare, this function
|
||||
// will be removed in a future version of this package to clean up the
|
||||
// namespace.
|
||||
func RegisterOrGet(c Collector) (Collector, error) {
|
||||
if err := Register(c); err != nil {
|
||||
if are, ok := err.(AlreadyRegisteredError); ok {
|
||||
return are.ExistingCollector, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// MustRegisterOrGet behaves like RegisterOrGet but panics instead of returning
|
||||
// an error.
|
||||
//
|
||||
// Deprecated: This is deprecated for the same reason RegisterOrGet is. See
|
||||
// there for details.
|
||||
func MustRegisterOrGet(c Collector) Collector {
|
||||
c, err := RegisterOrGet(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Unregister removes the registration of the provided Collector from the
|
||||
// DefaultRegisterer.
|
||||
//
|
||||
|
@ -201,25 +169,6 @@ func (gf GathererFunc) Gather() ([]*dto.MetricFamily, error) {
|
|||
return gf()
|
||||
}
|
||||
|
||||
// SetMetricFamilyInjectionHook replaces the DefaultGatherer with one that
|
||||
// gathers from the previous DefaultGatherers but then merges the MetricFamily
|
||||
// protobufs returned from the provided hook function with the MetricFamily
|
||||
// protobufs returned from the original DefaultGatherer.
|
||||
//
|
||||
// Deprecated: This function manipulates the DefaultGatherer variable. Consider
|
||||
// the implications, i.e. don't do this concurrently with any uses of the
|
||||
// DefaultGatherer. In the rare cases where you need to inject MetricFamily
|
||||
// protobufs directly, it is recommended to use a custom Registry and combine it
|
||||
// with a custom Gatherer using the Gatherers type (see
|
||||
// there). SetMetricFamilyInjectionHook only exists for compatibility reasons
|
||||
// with previous versions of this package.
|
||||
func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) {
|
||||
DefaultGatherer = Gatherers{
|
||||
DefaultGatherer,
|
||||
GathererFunc(func() ([]*dto.MetricFamily, error) { return hook(), nil }),
|
||||
}
|
||||
}
|
||||
|
||||
// AlreadyRegisteredError is returned by the Register method if the Collector to
|
||||
// be registered has already been registered before, or a different Collector
|
||||
// that collects the same metrics has been registered before. Registration fails
|
||||
|
@ -294,7 +243,7 @@ func (r *Registry) Register(c Collector) error {
|
|||
}()
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
// Coduct various tests...
|
||||
// Conduct various tests...
|
||||
for desc := range descChan {
|
||||
|
||||
// Is the descriptor valid at all?
|
||||
|
@ -447,7 +396,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
|
|||
|
||||
// Drain metricChan in case of premature return.
|
||||
defer func() {
|
||||
for _ = range metricChan {
|
||||
for range metricChan {
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -683,7 +632,7 @@ func (s metricSorter) Less(i, j int) bool {
|
|||
return s[i].GetTimestampMs() < s[j].GetTimestampMs()
|
||||
}
|
||||
|
||||
// normalizeMetricFamilies returns a MetricFamily slice whith empty
|
||||
// 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 {
|
||||
|
|
17
vendor/github.com/prometheus/client_golang/prometheus/summary.go
generated
vendored
17
vendor/github.com/prometheus/client_golang/prometheus/summary.go
generated
vendored
|
@ -54,6 +54,9 @@ type Summary interface {
|
|||
}
|
||||
|
||||
// DefObjectives are the default Summary quantile values.
|
||||
//
|
||||
// Deprecated: DefObjectives will not be used as the default objectives in
|
||||
// v0.10 of the library. The default Summary will have no quantiles then.
|
||||
var (
|
||||
DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
|
||||
|
||||
|
@ -113,9 +116,15 @@ type SummaryOpts struct {
|
|||
ConstLabels Labels
|
||||
|
||||
// Objectives defines the quantile rank estimates with their respective
|
||||
// absolute error. If Objectives[q] = e, then the value reported
|
||||
// for q will be the φ-quantile value for some φ between q-e and q+e.
|
||||
// The default value is DefObjectives.
|
||||
// absolute error. If Objectives[q] = e, then the value reported for q
|
||||
// will be the φ-quantile value for some φ between q-e and q+e. The
|
||||
// default value is DefObjectives. It is used if Objectives is left at
|
||||
// its zero value (i.e. nil). To create a Summary without Objectives,
|
||||
// set it to an empty map (i.e. map[float64]float64{}).
|
||||
//
|
||||
// Deprecated: Note that the current value of DefObjectives is
|
||||
// deprecated. It will be replaced by an empty map in v0.10 of the
|
||||
// library. Please explicitly set Objectives to the desired value.
|
||||
Objectives map[float64]float64
|
||||
|
||||
// MaxAge defines the duration for which an observation stays relevant
|
||||
|
@ -183,7 +192,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
|||
}
|
||||
}
|
||||
|
||||
if len(opts.Objectives) == 0 {
|
||||
if opts.Objectives == nil {
|
||||
opts.Objectives = DefObjectives
|
||||
}
|
||||
|
||||
|
|
74
vendor/github.com/prometheus/client_golang/prometheus/timer.go
generated
vendored
Normal file
74
vendor/github.com/prometheus/client_golang/prometheus/timer.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
// 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"
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (t *Timer) ObserveDuration() {
|
||||
if t.observer != nil {
|
||||
t.observer.Observe(time.Since(t.begin).Seconds())
|
||||
}
|
||||
}
|
5
vendor/github.com/prometheus/client_golang/prometheus/untyped.go
generated
vendored
5
vendor/github.com/prometheus/client_golang/prometheus/untyped.go
generated
vendored
|
@ -20,6 +20,11 @@ package prometheus
|
|||
// no type information is implied.
|
||||
//
|
||||
// To create Untyped instances, use NewUntyped.
|
||||
//
|
||||
// Deprecated: The Untyped type is deprecated because it doesn't make sense in
|
||||
// direct instrumentation. If you need to mirror an external metric of unknown
|
||||
// type (usually while writing exporters), Use MustNewConstMetric to create an
|
||||
// untyped metric instance on the fly.
|
||||
type Untyped interface {
|
||||
Metric
|
||||
Collector
|
||||
|
|
7
vendor/github.com/prometheus/client_golang/prometheus/value.go
generated
vendored
7
vendor/github.com/prometheus/client_golang/prometheus/value.go
generated
vendored
|
@ -19,6 +19,7 @@ import (
|
|||
"math"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
|
@ -43,7 +44,7 @@ var errInconsistentCardinality = errors.New("inconsistent label cardinality")
|
|||
// ValueType. This is a low-level building block used by the library to back the
|
||||
// implementations of Counter, Gauge, and Untyped.
|
||||
type value struct {
|
||||
// valBits containst the bits of the represented float64 value. It has
|
||||
// valBits contains the bits of the represented float64 value. It has
|
||||
// to go first in the struct to guarantee alignment for atomic
|
||||
// operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||
valBits uint64
|
||||
|
@ -80,6 +81,10 @@ func (v *value) Set(val float64) {
|
|||
atomic.StoreUint64(&v.valBits, math.Float64bits(val))
|
||||
}
|
||||
|
||||
func (v *value) SetToCurrentTime() {
|
||||
v.Set(float64(time.Now().UnixNano()) / 1e9)
|
||||
}
|
||||
|
||||
func (v *value) Inc() {
|
||||
v.Add(1)
|
||||
}
|
||||
|
|
18
vendor/vendor.json
vendored
18
vendor/vendor.json
vendored
|
@ -603,10 +603,10 @@
|
|||
"revisionTime": "2016-06-15T09:26:46Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "KkB+77Ziom7N6RzSbyUwYGrmDeU=",
|
||||
"checksumSHA1": "d2irkxoHgazkTuLIvJGiYwagl8o=",
|
||||
"path": "github.com/prometheus/client_golang/prometheus",
|
||||
"revision": "c5b7fccd204277076155f10851dad72b76a49317",
|
||||
"revisionTime": "2016-08-17T15:48:24Z"
|
||||
"revision": "08fd2e12372a66e68e30523c7642e0cbc3e4fbde",
|
||||
"revisionTime": "2017-04-01T10:34:46Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "DvwvOlPNAgRntBzt3b3OSRMS2N4=",
|
||||
|
@ -615,10 +615,10 @@
|
|||
"revisionTime": "2015-02-12T10:17:44Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "jG8qYuDUuaZeflt4JxBBdyQBsXw=",
|
||||
"checksumSHA1": "Wtpzndm/+bdwwNU5PCTfb4oUhc8=",
|
||||
"path": "github.com/prometheus/common/expfmt",
|
||||
"revision": "dd2f054febf4a6c00f2343686efb775948a8bff4",
|
||||
"revisionTime": "2017-01-08T23:12:12Z"
|
||||
"revision": "49fee292b27bfff7f354ee0f64e1bc4850462edf",
|
||||
"revisionTime": "2017-02-20T10:38:46Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "GWlM3d2vPYyNATtTFgftS10/A9w=",
|
||||
|
@ -633,10 +633,10 @@
|
|||
"revisionTime": "2017-01-08T23:12:12Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "vopCLXHzYm+3l5fPKOf4/fQwrCM=",
|
||||
"checksumSHA1": "0LL9u9tfv1KPBjNEiMDP6q7lpog=",
|
||||
"path": "github.com/prometheus/common/model",
|
||||
"revision": "3007b6072c17c8d985734e6e19b1dea9174e13d3",
|
||||
"revisionTime": "2017-02-19T00:35:58+01:00"
|
||||
"revision": "49fee292b27bfff7f354ee0f64e1bc4850462edf",
|
||||
"revisionTime": "2017-02-20T10:38:46Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "ZbbESWBHHcPUJ/A5yrzKhTHuPc8=",
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -50,6 +51,7 @@ const (
|
|||
errorCanceled = "canceled"
|
||||
errorExec = "execution"
|
||||
errorBadData = "bad_data"
|
||||
errorInternal = "internal"
|
||||
)
|
||||
|
||||
var corsHeaders = map[string]string{
|
||||
|
@ -73,7 +75,7 @@ type targetRetriever interface {
|
|||
}
|
||||
|
||||
type alertmanagerRetriever interface {
|
||||
Alertmanagers() []string
|
||||
Alertmanagers() []*url.URL
|
||||
}
|
||||
|
||||
type response struct {
|
||||
|
@ -194,6 +196,8 @@ func (api *API) query(r *http.Request) (interface{}, *apiError) {
|
|||
return nil, &apiError{errorCanceled, res.Err}
|
||||
case promql.ErrQueryTimeout:
|
||||
return nil, &apiError{errorTimeout, res.Err}
|
||||
case promql.ErrStorage:
|
||||
return nil, &apiError{errorInternal, res.Err}
|
||||
}
|
||||
return nil, &apiError{errorExec, res.Err}
|
||||
}
|
||||
|
@ -362,7 +366,7 @@ func (api *API) dropSeries(r *http.Request) (interface{}, *apiError) {
|
|||
}
|
||||
|
||||
// TODO(fabxc): temporarily disabled
|
||||
panic("disabled")
|
||||
return nil, &apiError{errorExec, fmt.Errorf("temporarily disabled")}
|
||||
|
||||
// numDeleted := 0
|
||||
// for _, s := range r.Form["match[]"] {
|
||||
|
@ -442,8 +446,8 @@ func (api *API) alertmanagers(r *http.Request) (interface{}, *apiError) {
|
|||
urls := api.alertmanagerRetriever.Alertmanagers()
|
||||
ams := &AlertmanagerDiscovery{ActiveAlertmanagers: make([]*AlertmanagerTarget, len(urls))}
|
||||
|
||||
for i := range urls {
|
||||
ams.ActiveAlertmanagers[i] = &AlertmanagerTarget{URL: urls[i]}
|
||||
for i, url := range urls {
|
||||
ams.ActiveAlertmanagers[i] = &AlertmanagerTarget{URL: url.String()}
|
||||
}
|
||||
|
||||
return ams, nil
|
||||
|
@ -474,6 +478,8 @@ func respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) {
|
|||
code = 422
|
||||
case errorCanceled, errorTimeout:
|
||||
code = http.StatusServiceUnavailable
|
||||
case errorInternal:
|
||||
code = http.StatusInternalServerError
|
||||
default:
|
||||
code = http.StatusInternalServerError
|
||||
}
|
||||
|
|
|
@ -41,9 +41,9 @@ func (f targetRetrieverFunc) Targets() []*retrieval.Target {
|
|||
return f()
|
||||
}
|
||||
|
||||
type alertmanagerRetrieverFunc func() []string
|
||||
type alertmanagerRetrieverFunc func() []*url.URL
|
||||
|
||||
func (f alertmanagerRetrieverFunc) Alertmanagers() []string {
|
||||
func (f alertmanagerRetrieverFunc) Alertmanagers() []*url.URL {
|
||||
return f()
|
||||
}
|
||||
|
||||
|
@ -79,8 +79,12 @@ func TestEndpoints(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
ar := alertmanagerRetrieverFunc(func() []string {
|
||||
return []string{"http://alertmanager.example.com:8080/api/v1/alerts"}
|
||||
ar := alertmanagerRetrieverFunc(func() []*url.URL {
|
||||
return []*url.URL{{
|
||||
Scheme: "http",
|
||||
Host: "alertmanager.example.com:8080",
|
||||
Path: "/api/v1/alerts",
|
||||
}}
|
||||
})
|
||||
|
||||
api := &API{
|
||||
|
@ -430,15 +434,27 @@ func TestEndpoints(t *testing.T) {
|
|||
// }{2},
|
||||
// }, {
|
||||
// endpoint: api.targets,
|
||||
// response: []*Target{
|
||||
// &Target{
|
||||
// DiscoveredLabels: nil,
|
||||
// Labels: nil,
|
||||
// ScrapeUrl: "http://example.com:8080/metrics",
|
||||
// Health: "unknown",
|
||||
// response: &TargetDiscovery{
|
||||
// ActiveTargets: []*Target{
|
||||
// {
|
||||
// DiscoveredLabels: model.LabelSet{},
|
||||
// Labels: model.LabelSet{},
|
||||
// ScrapeURL: "http://example.com:8080/metrics",
|
||||
// Health: "unknown",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
{
|
||||
endpoint: api.alertmanagers,
|
||||
response: &AlertmanagerDiscovery{
|
||||
ActiveAlertmanagers: []*AlertmanagerTarget{
|
||||
{
|
||||
URL: "http://alertmanager.example.com:8080/api/v1/alerts",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -44,23 +44,27 @@ body {
|
|||
.legend {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 0 0 0 40px;
|
||||
margin: 0 0 0 60px;
|
||||
}
|
||||
|
||||
.graph_area {
|
||||
position: relative;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
margin: 5px 0 5px 0;
|
||||
margin: 5px 0 5px 20px;
|
||||
}
|
||||
|
||||
.y_axis {
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.y_axis svg {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.graph .detail .item.active {
|
||||
line-height: 1.4em;
|
||||
padding: 0.5em;
|
||||
|
@ -126,7 +130,7 @@ input[name="end_input"], input[name="range_input"] {
|
|||
}
|
||||
|
||||
.prometheus_input_group.range_input {
|
||||
margin-left: 39px;
|
||||
margin-left: 59px;
|
||||
}
|
||||
|
||||
.prometheus_input_group .btn {
|
||||
|
|
|
@ -415,7 +415,7 @@ Prometheus.Graph.prototype.submitQuery = function() {
|
|||
return;
|
||||
}
|
||||
var duration = new Date().getTime() - startTime;
|
||||
var totalTimeSeries = xhr.responseJSON.data.result.length;
|
||||
var totalTimeSeries = (xhr.responseJSON.data !== undefined) ? xhr.responseJSON.data.result.length : 0;
|
||||
self.evalStats.html("Load time: " + duration + "ms <br /> Resolution: " + resolution + "s <br />" + "Total time series: " + totalTimeSeries);
|
||||
self.spinner.hide();
|
||||
}
|
||||
|
@ -556,6 +556,27 @@ Prometheus.Graph.prototype.updateGraph = function() {
|
|||
min: "auto",
|
||||
});
|
||||
|
||||
// Find and set graph's max/min
|
||||
var min = Infinity;
|
||||
var max = -Infinity;
|
||||
self.data.forEach(function(timeSeries) {
|
||||
timeSeries.data.forEach(function(dataPoint) {
|
||||
if (dataPoint.y < min && dataPoint.y != null) {
|
||||
min = dataPoint.y;
|
||||
}
|
||||
if (dataPoint.y > max && dataPoint.y != null) {
|
||||
max = dataPoint.y;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (min === max) {
|
||||
self.rickshawGraph.max = max + 1;
|
||||
self.rickshawGraph.min = min - 1;
|
||||
} else {
|
||||
self.rickshawGraph.max = max + (0.1*(Math.abs(max - min)));
|
||||
self.rickshawGraph.min = min - (0.1*(Math.abs(max - min)));
|
||||
}
|
||||
|
||||
var xAxis = new Rickshaw.Graph.Axis.Time({ graph: self.rickshawGraph });
|
||||
|
||||
var yAxis = new Rickshaw.Graph.Axis.Y({
|
||||
|
|
|
@ -578,7 +578,7 @@ PromConsole.Graph.prototype.dispatch = function() {
|
|||
}
|
||||
|
||||
var loadingImg = document.createElement("img");
|
||||
loadingImg.src = PATH_PREFIX + '/static/img/ajax-loader.gif?v=' + BUILD_VERSION;
|
||||
loadingImg.src = PATH_PREFIX + '/static/img/ajax-loader.gif';
|
||||
loadingImg.alt = 'Loading...';
|
||||
loadingImg.className = 'prom_graph_loading';
|
||||
this.graphTd.appendChild(loadingImg);
|
||||
|
|
|
@ -54,7 +54,8 @@
|
|||
</tr>
|
||||
{{range .Alertmanagers}}
|
||||
<tr>
|
||||
<td>{{.}}</td>
|
||||
{{/* Alertmanager URLs always have Scheme, Host and Path set */}}
|
||||
<td>{{.Scheme}}://<a href="{{.Scheme}}://{{.Host}}">{{.Host}}</a>{{.Path}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
|
|
|
@ -348,7 +348,7 @@ func (h *Handler) status(w http.ResponseWriter, r *http.Request) {
|
|||
Birth time.Time
|
||||
CWD string
|
||||
Version *PrometheusVersion
|
||||
Alertmanagers []string
|
||||
Alertmanagers []*url.URL
|
||||
}{
|
||||
Birth: h.birth,
|
||||
CWD: h.cwd,
|
||||
|
|
Loading…
Reference in a new issue