Merge pull request #214 from prometheus/beorn7/registry
Create a public registry interface and separate out HTTP exposition
This commit is contained in:
commit
66058aac3a
5
NOTICE
5
NOTICE
|
@ -7,11 +7,6 @@ SoundCloud Ltd. (http://soundcloud.com/).
|
||||||
|
|
||||||
The following components are included in this product:
|
The following components are included in this product:
|
||||||
|
|
||||||
goautoneg
|
|
||||||
http://bitbucket.org/ww/goautoneg
|
|
||||||
Copyright 2011, Open Knowledge Foundation Ltd.
|
|
||||||
See README.txt for license details.
|
|
||||||
|
|
||||||
perks - a fork of https://github.com/bmizerany/perks
|
perks - a fork of https://github.com/bmizerany/perks
|
||||||
https://github.com/beorn7/perks
|
https://github.com/beorn7/perks
|
||||||
Copyright 2013-2015 Blake Mizerany, Björn Rabenstein
|
Copyright 2013-2015 Blake Mizerany, Björn Rabenstein
|
||||||
|
|
|
@ -1,53 +1 @@
|
||||||
# Overview
|
See [![go-doc](https://godoc.org/github.com/prometheus/client_golang/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/prometheus).
|
||||||
This is the [Prometheus](http://www.prometheus.io) telemetric
|
|
||||||
instrumentation client [Go](http://golang.org) client library. It
|
|
||||||
enable authors to define process-space metrics for their servers and
|
|
||||||
expose them through a web service interface for extraction,
|
|
||||||
aggregation, and a whole slew of other post processing techniques.
|
|
||||||
|
|
||||||
# Installing
|
|
||||||
$ go get github.com/prometheus/client_golang/prometheus
|
|
||||||
|
|
||||||
# Example
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
indexed = prometheus.NewCounter(prometheus.CounterOpts{
|
|
||||||
Namespace: "my_company",
|
|
||||||
Subsystem: "indexer",
|
|
||||||
Name: "documents_indexed",
|
|
||||||
Help: "The number of documents indexed.",
|
|
||||||
})
|
|
||||||
size = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Namespace: "my_company",
|
|
||||||
Subsystem: "storage",
|
|
||||||
Name: "documents_total_size_bytes",
|
|
||||||
Help: "The total size of all documents in the storage.",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.Handle("/metrics", prometheus.Handler())
|
|
||||||
|
|
||||||
indexed.Inc()
|
|
||||||
size.Set(5)
|
|
||||||
|
|
||||||
http.ListenAndServe(":8080", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
prometheus.MustRegister(indexed)
|
|
||||||
prometheus.MustRegister(size)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/prometheus/client_golang?status.png)](https://godoc.org/github.com/prometheus/client_golang)
|
|
||||||
|
|
|
@ -15,15 +15,15 @@ package prometheus
|
||||||
|
|
||||||
// Collector is the interface implemented by anything that can be used by
|
// Collector is the interface implemented by anything that can be used by
|
||||||
// Prometheus to collect metrics. A Collector has to be registered for
|
// Prometheus to collect metrics. A Collector has to be registered for
|
||||||
// collection. See Register, MustRegister, RegisterOrGet, and MustRegisterOrGet.
|
// collection. See Registerer.Register.
|
||||||
//
|
//
|
||||||
// The stock metrics provided by this package (like Gauge, Counter, Summary) are
|
// The stock metrics provided by this package (Gauge, Counter, Summary,
|
||||||
// also Collectors (which only ever collect one metric, namely itself). An
|
// Histogram, Untyped) are also Collectors (which only ever collect one metric,
|
||||||
// implementer of Collector may, however, collect multiple metrics in a
|
// namely itself). An implementer of Collector may, however, collect multiple
|
||||||
// coordinated fashion and/or create metrics on the fly. Examples for collectors
|
// metrics in a coordinated fashion and/or create metrics on the fly. Examples
|
||||||
// already implemented in this library are the metric vectors (i.e. collection
|
// for collectors already implemented in this library are the metric vectors
|
||||||
// of multiple instances of the same Metric but with different label values)
|
// (i.e. collection of multiple instances of the same Metric but with different
|
||||||
// like GaugeVec or SummaryVec, and the ExpvarCollector.
|
// label values) like GaugeVec or SummaryVec, and the ExpvarCollector.
|
||||||
type Collector interface {
|
type Collector interface {
|
||||||
// Describe sends the super-set of all possible descriptors of metrics
|
// Describe sends the super-set of all possible descriptors of metrics
|
||||||
// collected by this Collector to the provided channel and returns once
|
// collected by this Collector to the provided channel and returns once
|
||||||
|
@ -37,39 +37,39 @@ type Collector interface {
|
||||||
// executing this method, it must send an invalid descriptor (created
|
// executing this method, it must send an invalid descriptor (created
|
||||||
// with NewInvalidDesc) to signal the error to the registry.
|
// with NewInvalidDesc) to signal the error to the registry.
|
||||||
Describe(chan<- *Desc)
|
Describe(chan<- *Desc)
|
||||||
// Collect is called by Prometheus when collecting metrics. The
|
// Collect is called by the Prometheus registry when collecting
|
||||||
// implementation sends each collected metric via the provided channel
|
// metrics. The implementation sends each collected metric via the
|
||||||
// and returns once the last metric has been sent. The descriptor of
|
// provided channel and returns once the last metric has been sent. The
|
||||||
// each sent metric is one of those returned by Describe. Returned
|
// descriptor of each sent metric is one of those returned by
|
||||||
// metrics that share the same descriptor must differ in their variable
|
// Describe. Returned metrics that share the same descriptor must differ
|
||||||
// label values. This method may be called concurrently and must
|
// in their variable label values. This method may be called
|
||||||
// therefore be implemented in a concurrency safe way. Blocking occurs
|
// concurrently and must therefore be implemented in a concurrency safe
|
||||||
// at the expense of total performance of rendering all registered
|
// way. Blocking occurs at the expense of total performance of rendering
|
||||||
// metrics. Ideally, Collector implementations support concurrent
|
// all registered metrics. Ideally, Collector implementations support
|
||||||
// readers.
|
// concurrent readers.
|
||||||
Collect(chan<- Metric)
|
Collect(chan<- Metric)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelfCollector implements Collector for a single Metric so that that the
|
// selfCollector implements Collector for a single Metric so that the Metric
|
||||||
// Metric collects itself. Add it as an anonymous field to a struct that
|
// collects itself. Add it as an anonymous field to a struct that implements
|
||||||
// implements Metric, and call Init with the Metric itself as an argument.
|
// Metric, and call init with the Metric itself as an argument.
|
||||||
type SelfCollector struct {
|
type selfCollector struct {
|
||||||
self Metric
|
self Metric
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init provides the SelfCollector with a reference to the metric it is supposed
|
// init provides the selfCollector with a reference to the metric it is supposed
|
||||||
// to collect. It is usually called within the factory function to create a
|
// to collect. It is usually called within the factory function to create a
|
||||||
// metric. See example.
|
// metric. See example.
|
||||||
func (c *SelfCollector) Init(self Metric) {
|
func (c *selfCollector) init(self Metric) {
|
||||||
c.self = self
|
c.self = self
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describe implements Collector.
|
// Describe implements Collector.
|
||||||
func (c *SelfCollector) Describe(ch chan<- *Desc) {
|
func (c *selfCollector) Describe(ch chan<- *Desc) {
|
||||||
ch <- c.self.Desc()
|
ch <- c.self.Desc()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect implements Collector.
|
// Collect implements Collector.
|
||||||
func (c *SelfCollector) Collect(ch chan<- Metric) {
|
func (c *selfCollector) Collect(ch chan<- Metric) {
|
||||||
ch <- c.self
|
ch <- c.self
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,9 @@ type Counter interface {
|
||||||
// Prometheus metric. Do not use it for regular handling of a
|
// Prometheus metric. Do not use it for regular handling of a
|
||||||
// Prometheus counter (as it can be used to break the contract of
|
// Prometheus counter (as it can be used to break the contract of
|
||||||
// monotonically increasing values).
|
// monotonically increasing values).
|
||||||
|
//
|
||||||
|
// Deprecated: Use NewConstMetric to create a counter for an external
|
||||||
|
// value. A Counter should never be set.
|
||||||
Set(float64)
|
Set(float64)
|
||||||
// Inc increments the counter by 1.
|
// Inc increments the counter by 1.
|
||||||
Inc()
|
Inc()
|
||||||
|
@ -55,7 +58,7 @@ func NewCounter(opts CounterOpts) Counter {
|
||||||
opts.ConstLabels,
|
opts.ConstLabels,
|
||||||
)
|
)
|
||||||
result := &counter{value: value{desc: desc, valType: CounterValue, labelPairs: desc.constLabelPairs}}
|
result := &counter{value: value{desc: desc, valType: CounterValue, labelPairs: desc.constLabelPairs}}
|
||||||
result.Init(result) // Init self-collection.
|
result.init(result) // Init self-collection.
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +105,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
|
||||||
valType: CounterValue,
|
valType: CounterValue,
|
||||||
labelPairs: makeLabelPairs(desc, lvs),
|
labelPairs: makeLabelPairs(desc, lvs),
|
||||||
}}
|
}}
|
||||||
result.Init(result) // Init self-collection.
|
result.init(result) // Init self-collection.
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
// 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
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -11,18 +11,17 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package prometheus provides embeddable metric primitives for servers and
|
// Package prometheus provides metrics primitives to instrument code for
|
||||||
// standardized exposition of telemetry through a web services interface.
|
// monitoring. It also offers a registry for metrics. Sub-packages allow to
|
||||||
|
// expose the registered metrics via HTTP (package promhttp) or push them to a
|
||||||
|
// Pushgateway (package push).
|
||||||
//
|
//
|
||||||
// All exported functions and methods are safe to be used concurrently unless
|
// All exported functions and methods are safe to be used concurrently unless
|
||||||
// specified otherwise.
|
//specified otherwise.
|
||||||
//
|
//
|
||||||
// To expose metrics registered with the Prometheus registry, an HTTP server
|
// A Basic Example
|
||||||
// needs to know about the Prometheus handler. The usual endpoint is "/metrics".
|
|
||||||
//
|
//
|
||||||
// http.Handle("/metrics", prometheus.Handler())
|
// As a starting point, a very basic usage example:
|
||||||
//
|
|
||||||
// As a starting point a very basic usage example:
|
|
||||||
//
|
//
|
||||||
// package main
|
// package main
|
||||||
//
|
//
|
||||||
|
@ -30,6 +29,7 @@
|
||||||
// "net/http"
|
// "net/http"
|
||||||
//
|
//
|
||||||
// "github.com/prometheus/client_golang/prometheus"
|
// "github.com/prometheus/client_golang/prometheus"
|
||||||
|
// "github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// var (
|
// var (
|
||||||
|
@ -37,75 +37,145 @@
|
||||||
// Name: "cpu_temperature_celsius",
|
// Name: "cpu_temperature_celsius",
|
||||||
// Help: "Current temperature of the CPU.",
|
// Help: "Current temperature of the CPU.",
|
||||||
// })
|
// })
|
||||||
// hdFailures = prometheus.NewCounter(prometheus.CounterOpts{
|
// hdFailures = prometheus.NewCounterVec(
|
||||||
// Name: "hd_errors_total",
|
// prometheus.CounterOpts{
|
||||||
// Help: "Number of hard-disk errors.",
|
// Name: "hd_errors_total",
|
||||||
// })
|
// Help: "Number of hard-disk errors.",
|
||||||
|
// },
|
||||||
|
// []string{"device"},
|
||||||
|
// )
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// func init() {
|
// func init() {
|
||||||
|
// // Metrics have to be registered to be exposed:
|
||||||
// prometheus.MustRegister(cpuTemp)
|
// prometheus.MustRegister(cpuTemp)
|
||||||
// prometheus.MustRegister(hdFailures)
|
// prometheus.MustRegister(hdFailures)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func main() {
|
// func main() {
|
||||||
// cpuTemp.Set(65.3)
|
// cpuTemp.Set(65.3)
|
||||||
// hdFailures.Inc()
|
// hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()
|
||||||
//
|
//
|
||||||
// http.Handle("/metrics", prometheus.Handler())
|
// // 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)
|
// http.ListenAndServe(":8080", nil)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// This is a complete program that exports two metrics, a Gauge and a Counter.
|
// This is a complete program that exports two metrics, a Gauge and a Counter,
|
||||||
// It also exports some stats about the HTTP usage of the /metrics
|
// the later with a label attached to turn it into a (one-dimensional) vector.
|
||||||
// endpoint. (See the Handler function for more detail.)
|
|
||||||
//
|
//
|
||||||
// Two more advanced metric types are the Summary and Histogram. A more
|
// Metrics
|
||||||
// thorough description of metric types can be found in the prometheus docs:
|
//
|
||||||
|
// The number of exported identifiers in this package might appear a bit
|
||||||
|
// overwhelming. Hovever, 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.
|
||||||
|
//
|
||||||
|
// Above, you have already touched the Counter and the Gauge. There are two more
|
||||||
|
// advanced metric types: the Summary and Histogram. A more thorough description
|
||||||
|
// of those four metric types can be found in the Prometheus docs:
|
||||||
// https://prometheus.io/docs/concepts/metric_types/
|
// https://prometheus.io/docs/concepts/metric_types/
|
||||||
//
|
//
|
||||||
// In addition to the fundamental metric types Gauge, Counter, Summary, and
|
// A fifth "type" of metric is Untyped. It behaves like a Gauge, but signals the
|
||||||
// Histogram, a very important part of the Prometheus data model is the
|
// Prometheus server not to assume anything about its type.
|
||||||
// partitioning of samples along dimensions called labels, which results in
|
//
|
||||||
|
// In addition to the fundamental metric types Gauge, Counter, Summary,
|
||||||
|
// Histogram, and Untyped, a very important part of the Prometheus data model is
|
||||||
|
// the partitioning of samples along dimensions called labels, which results in
|
||||||
// metric vectors. The fundamental types are GaugeVec, CounterVec, SummaryVec,
|
// metric vectors. The fundamental types are GaugeVec, CounterVec, SummaryVec,
|
||||||
// and HistogramVec.
|
// HistogramVec, and UntypedVec.
|
||||||
//
|
//
|
||||||
// Those are all the parts needed for basic usage. Detailed documentation and
|
// While only the fundamental metric types implement the Metric interface, both
|
||||||
// examples are provided below.
|
// the metrics and their vector versions implement the Collector interface. A
|
||||||
|
// Collector manages the collection of a number of Metrics, but for convenience,
|
||||||
|
// a Metric can also “collect itself”. Note that Gauge, Counter, Summary,
|
||||||
|
// Histogram, and Untyped are interfaces themselves while GaugeVec, CounterVec,
|
||||||
|
// SummaryVec, HistogramVec, and UntypedVec are not.
|
||||||
//
|
//
|
||||||
// Everything else this package offers is essentially for "power users" only. A
|
// To create instances of Metrics and their vector versions, you need a suitable
|
||||||
// few pointers to "power user features":
|
// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts,
|
||||||
|
// HistogramOpts, or UntypedOpts.
|
||||||
//
|
//
|
||||||
// All the various ...Opts structs have a ConstLabels field for labels that
|
// Custom Collectors and constant Metrics
|
||||||
// never change their value (which is only useful under special circumstances,
|
|
||||||
// see documentation of the Opts type).
|
|
||||||
//
|
//
|
||||||
// The Untyped metric behaves like a Gauge, but signals the Prometheus server
|
// While you could create your own implementations of Metric, most likely you
|
||||||
// not to assume anything about its type.
|
// will only ever implement the Collector interface on your own. At a first
|
||||||
|
// glance, a custom Collector seems handy to bundle Metrics for common
|
||||||
|
// registration (with the prime example of the different metric vectors above,
|
||||||
|
// which bundle all the metrics of the same name but with different labels).
|
||||||
//
|
//
|
||||||
// Functions to fine-tune how the metric registry works: EnableCollectChecks,
|
// There is a more involved use case, too: If you already have metrics
|
||||||
// PanicOnCollectError, Register, Unregister, SetMetricFamilyInjectionHook.
|
// available, created outside of the Prometheus context, you don't need the
|
||||||
|
// interface of the various Metric types. You essentially want to mirror the
|
||||||
|
// existing numbers into Prometheus Metrics during collection. An own
|
||||||
|
// implementation of the Collector interface is perfect for that. You can create
|
||||||
|
// 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.
|
||||||
//
|
//
|
||||||
// For custom metric collection, there are two entry points: Custom Metric
|
// The Collector example illustrates the use case. You can also look at the
|
||||||
// implementations and custom Collector implementations. A Metric is the
|
// source code of the processCollector (mirroring process metrics), the
|
||||||
// fundamental unit in the Prometheus data model: a sample at a point in time
|
// goCollector (mirroring Go metrics), or the expvarCollector (mirroring expvar
|
||||||
// together with its meta-data (like its fully-qualified name and any number of
|
// metrics) as examples that are used in this package itself.
|
||||||
// pairs of label name and label value) that knows how to marshal itself into a
|
|
||||||
// data transfer object (aka DTO, implemented as a protocol buffer). A Collector
|
|
||||||
// gets registered with the Prometheus registry and manages the collection of
|
|
||||||
// one or more Metrics. Many parts of this package are building blocks for
|
|
||||||
// Metrics and Collectors. Desc is the metric descriptor, actually used by all
|
|
||||||
// metrics under the hood, and by Collectors to describe the Metrics to be
|
|
||||||
// collected, but only to be dealt with by users if they implement their own
|
|
||||||
// Metrics or Collectors. To create a Desc, the BuildFQName function will come
|
|
||||||
// in handy. Other useful components for Metric and Collector implementation
|
|
||||||
// include: LabelPairSorter to sort the DTO version of label pairs,
|
|
||||||
// NewConstMetric and MustNewConstMetric to create "throw away" Metrics at
|
|
||||||
// collection time, MetricVec to bundle custom Metrics into a metric vector
|
|
||||||
// Collector, SelfCollector to make a custom Metric collect itself.
|
|
||||||
//
|
//
|
||||||
// A good example for a custom Collector is the ExpVarCollector included in this
|
// If you just need to call a function to get a single float value to collect as
|
||||||
// package, which exports variables exported via the "expvar" package as
|
// a metric, GaugeFunc, CounterFunc, or UntypedFunc might be interesting
|
||||||
// Prometheus metrics.
|
// shortcuts.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// NewProcessCollector). With a custom registry, you are in control and decide
|
||||||
|
// yourself about the Collectors to register.
|
||||||
|
//
|
||||||
|
// HTTP Exposition
|
||||||
|
//
|
||||||
|
// The Registry implements the Gatherer interface. The caller of the Gather
|
||||||
|
// method can then expose the gathered metrics in some way. Usually, the metrics
|
||||||
|
// are served via HTTP on the /metrics endpoint. That's happening in the example
|
||||||
|
// above. The tools to expose metrics via HTTP are in the promhttp
|
||||||
|
// sub-package. (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.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
|
@ -13,11 +13,7 @@
|
||||||
|
|
||||||
package prometheus_test
|
package prometheus_test
|
||||||
|
|
||||||
import (
|
import "github.com/prometheus/client_golang/prometheus"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClusterManager is an example for a system that might have been built without
|
// ClusterManager is an example for a system that might have been built without
|
||||||
// Prometheus in mind. It models a central manager of jobs running in a
|
// Prometheus in mind. It models a central manager of jobs running in a
|
||||||
|
@ -29,10 +25,9 @@ import (
|
||||||
// make use of ConstLabels to be able to register each ClusterManager instance
|
// make use of ConstLabels to be able to register each ClusterManager instance
|
||||||
// with Prometheus.
|
// with Prometheus.
|
||||||
type ClusterManager struct {
|
type ClusterManager struct {
|
||||||
Zone string
|
Zone string
|
||||||
OOMCount *prometheus.CounterVec
|
OOMCountDesc *prometheus.Desc
|
||||||
RAMUsage *prometheus.GaugeVec
|
RAMUsageDesc *prometheus.Desc
|
||||||
mtx sync.Mutex // Protects OOMCount and RAMUsage.
|
|
||||||
// ... many more fields
|
// ... many more fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,76 +50,69 @@ func (c *ClusterManager) ReallyExpensiveAssessmentOfTheSystemState() (
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describe faces the interesting challenge that the two metric vectors that are
|
// Describe simply sends the two Descs in the struct to the channel.
|
||||||
// used in this example are already Collectors themselves. However, thanks to
|
|
||||||
// the use of channels, it is really easy to "chain" Collectors. Here we simply
|
|
||||||
// call the Describe methods of the two metric vectors.
|
|
||||||
func (c *ClusterManager) Describe(ch chan<- *prometheus.Desc) {
|
func (c *ClusterManager) Describe(ch chan<- *prometheus.Desc) {
|
||||||
c.OOMCount.Describe(ch)
|
ch <- c.OOMCountDesc
|
||||||
c.RAMUsage.Describe(ch)
|
ch <- c.RAMUsageDesc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect first triggers the ReallyExpensiveAssessmentOfTheSystemState. Then it
|
// Collect first triggers the ReallyExpensiveAssessmentOfTheSystemState. Then it
|
||||||
// sets the retrieved values in the two metric vectors and then sends all their
|
// creates constant metrics for each host on the fly based on the returned data.
|
||||||
// metrics to the channel (again using a chaining technique as in the Describe
|
//
|
||||||
// method). Since Collect could be called multiple times concurrently, that part
|
// Note that Collect could be called concurrently, so we depend on
|
||||||
// is protected by a mutex.
|
// ReallyExpensiveAssessmentOfTheSystemState to be concurrency-safe.
|
||||||
func (c *ClusterManager) Collect(ch chan<- prometheus.Metric) {
|
func (c *ClusterManager) Collect(ch chan<- prometheus.Metric) {
|
||||||
oomCountByHost, ramUsageByHost := c.ReallyExpensiveAssessmentOfTheSystemState()
|
oomCountByHost, ramUsageByHost := c.ReallyExpensiveAssessmentOfTheSystemState()
|
||||||
c.mtx.Lock()
|
|
||||||
defer c.mtx.Unlock()
|
|
||||||
for host, oomCount := range oomCountByHost {
|
for host, oomCount := range oomCountByHost {
|
||||||
c.OOMCount.WithLabelValues(host).Set(float64(oomCount))
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.OOMCountDesc,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
float64(oomCount),
|
||||||
|
host,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
for host, ramUsage := range ramUsageByHost {
|
for host, ramUsage := range ramUsageByHost {
|
||||||
c.RAMUsage.WithLabelValues(host).Set(ramUsage)
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.RAMUsageDesc,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
ramUsage,
|
||||||
|
host,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
c.OOMCount.Collect(ch)
|
|
||||||
c.RAMUsage.Collect(ch)
|
|
||||||
// All metrics in OOMCount and RAMUsage are sent to the channel now. We
|
|
||||||
// can safely reset the two metric vectors now, so that we can start
|
|
||||||
// fresh in the next Collect cycle. (Imagine a host disappears from the
|
|
||||||
// cluster. If we did not reset here, its Metric would stay in the
|
|
||||||
// metric vectors forever.)
|
|
||||||
c.OOMCount.Reset()
|
|
||||||
c.RAMUsage.Reset()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClusterManager creates the two metric vectors OOMCount and RAMUsage. Note
|
// NewClusterManager creates the two Descs OOMCountDesc and RAMUsageDesc. Note
|
||||||
// that the zone is set as a ConstLabel. (It's different in each instance of the
|
// that the zone is set as a ConstLabel. (It's different in each instance of the
|
||||||
// ClusterManager, but constant over the lifetime of an instance.) The reported
|
// ClusterManager, but constant over the lifetime of an instance.) Then there is
|
||||||
// values are partitioned by host, which is therefore a variable label.
|
// a variable label "host", since we want to partition the collected metrics by
|
||||||
|
// host. Since all Descs created in this way are consistent across instances,
|
||||||
|
// with a guaranteed distinction by the "zone" label, we can register different
|
||||||
|
// ClusterManager instances with the same registry.
|
||||||
func NewClusterManager(zone string) *ClusterManager {
|
func NewClusterManager(zone string) *ClusterManager {
|
||||||
return &ClusterManager{
|
return &ClusterManager{
|
||||||
Zone: zone,
|
Zone: zone,
|
||||||
OOMCount: prometheus.NewCounterVec(
|
OOMCountDesc: prometheus.NewDesc(
|
||||||
prometheus.CounterOpts{
|
"clustermanager_oom_crashes_total",
|
||||||
Subsystem: "clustermanager",
|
"Number of OOM crashes.",
|
||||||
Name: "oom_count",
|
|
||||||
Help: "number of OOM crashes",
|
|
||||||
ConstLabels: prometheus.Labels{"zone": zone},
|
|
||||||
},
|
|
||||||
[]string{"host"},
|
[]string{"host"},
|
||||||
|
prometheus.Labels{"zone": zone},
|
||||||
),
|
),
|
||||||
RAMUsage: prometheus.NewGaugeVec(
|
RAMUsageDesc: prometheus.NewDesc(
|
||||||
prometheus.GaugeOpts{
|
"clustermanager_ram_usage_bytes",
|
||||||
Subsystem: "clustermanager",
|
"RAM usage as reported to the cluster manager.",
|
||||||
Name: "ram_usage_bytes",
|
|
||||||
Help: "RAM usage as reported to the cluster manager",
|
|
||||||
ConstLabels: prometheus.Labels{"zone": zone},
|
|
||||||
},
|
|
||||||
[]string{"host"},
|
[]string{"host"},
|
||||||
|
prometheus.Labels{"zone": zone},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleCollector_clustermanager() {
|
func ExampleCollector() {
|
||||||
workerDB := NewClusterManager("db")
|
workerDB := NewClusterManager("db")
|
||||||
workerCA := NewClusterManager("ca")
|
workerCA := NewClusterManager("ca")
|
||||||
prometheus.MustRegister(workerDB)
|
|
||||||
prometheus.MustRegister(workerCA)
|
|
||||||
|
|
||||||
// Since we are dealing with custom Collector implementations, it might
|
// Since we are dealing with custom Collector implementations, it might
|
||||||
// be a good idea to enable the collect checks in the registry.
|
// be a good idea to try it out with a pedantic registry.
|
||||||
prometheus.EnableCollectChecks(true)
|
reg := prometheus.NewPedanticRegistry()
|
||||||
|
reg.MustRegister(workerDB)
|
||||||
|
reg.MustRegister(workerCA)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
allocDesc = prometheus.NewDesc(
|
|
||||||
prometheus.BuildFQName("", "memstats", "alloc_bytes"),
|
|
||||||
"bytes allocated and still in use",
|
|
||||||
nil, nil,
|
|
||||||
)
|
|
||||||
totalAllocDesc = prometheus.NewDesc(
|
|
||||||
prometheus.BuildFQName("", "memstats", "total_alloc_bytes"),
|
|
||||||
"bytes allocated (even if freed)",
|
|
||||||
nil, nil,
|
|
||||||
)
|
|
||||||
numGCDesc = prometheus.NewDesc(
|
|
||||||
prometheus.BuildFQName("", "memstats", "num_gc_total"),
|
|
||||||
"number of GCs run",
|
|
||||||
nil, nil,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// MemStatsCollector is an example for a custom Collector that solves the
|
|
||||||
// problem of feeding into multiple metrics at the same time. The
|
|
||||||
// runtime.ReadMemStats should happen only once, and then the results need to be
|
|
||||||
// fed into a number of separate Metrics. In this example, only a few of the
|
|
||||||
// values reported by ReadMemStats are used. For each, there is a Desc provided
|
|
||||||
// as a var, so the MemStatsCollector itself needs nothing else in the
|
|
||||||
// struct. Only the methods need to be implemented.
|
|
||||||
type MemStatsCollector struct{}
|
|
||||||
|
|
||||||
// Describe just sends the three Desc objects for the Metrics we intend to
|
|
||||||
// collect.
|
|
||||||
func (_ MemStatsCollector) Describe(ch chan<- *prometheus.Desc) {
|
|
||||||
ch <- allocDesc
|
|
||||||
ch <- totalAllocDesc
|
|
||||||
ch <- numGCDesc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect does the trick by calling ReadMemStats once and then constructing
|
|
||||||
// three different Metrics on the fly.
|
|
||||||
func (_ MemStatsCollector) Collect(ch chan<- prometheus.Metric) {
|
|
||||||
var ms runtime.MemStats
|
|
||||||
runtime.ReadMemStats(&ms)
|
|
||||||
ch <- prometheus.MustNewConstMetric(
|
|
||||||
allocDesc,
|
|
||||||
prometheus.GaugeValue,
|
|
||||||
float64(ms.Alloc),
|
|
||||||
)
|
|
||||||
ch <- prometheus.MustNewConstMetric(
|
|
||||||
totalAllocDesc,
|
|
||||||
prometheus.GaugeValue,
|
|
||||||
float64(ms.TotalAlloc),
|
|
||||||
)
|
|
||||||
ch <- prometheus.MustNewConstMetric(
|
|
||||||
numGCDesc,
|
|
||||||
prometheus.CounterValue,
|
|
||||||
float64(ms.NumGC),
|
|
||||||
)
|
|
||||||
// To avoid new allocations on each collection, you could also keep
|
|
||||||
// metric objects around and return the same objects each time, just
|
|
||||||
// with new values set.
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleCollector_memstats() {
|
|
||||||
prometheus.MustRegister(&MemStatsCollector{})
|
|
||||||
// Since we are dealing with custom Collector implementations, it might
|
|
||||||
// be a good idea to enable the collect checks in the registry.
|
|
||||||
prometheus.EnableCollectChecks(true)
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewCallbackMetric(desc *prometheus.Desc, callback func() float64) *CallbackMetric {
|
|
||||||
result := &CallbackMetric{desc: desc, callback: callback}
|
|
||||||
result.Init(result) // Initialize the SelfCollector.
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Come up with a better example.
|
|
||||||
|
|
||||||
// CallbackMetric is an example for a user-defined Metric that exports the
|
|
||||||
// result of a function call as a metric of type "untyped" without any
|
|
||||||
// labels. It uses SelfCollector to turn the Metric into a Collector so that it
|
|
||||||
// can be registered with Prometheus.
|
|
||||||
//
|
|
||||||
// Note that this example is pretty much academic as the prometheus package
|
|
||||||
// already provides an UntypedFunc type.
|
|
||||||
type CallbackMetric struct {
|
|
||||||
prometheus.SelfCollector
|
|
||||||
|
|
||||||
desc *prometheus.Desc
|
|
||||||
callback func() float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cm *CallbackMetric) Desc() *prometheus.Desc {
|
|
||||||
return cm.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cm *CallbackMetric) Write(m *dto.Metric) error {
|
|
||||||
m.Untyped = &dto.Untyped{Value: proto.Float64(cm.callback())}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleSelfCollector() {
|
|
||||||
m := NewCallbackMetric(
|
|
||||||
prometheus.NewDesc(
|
|
||||||
"runtime_goroutines_count",
|
|
||||||
"Total number of goroutines that currently exist.",
|
|
||||||
nil, nil, // No labels, these must be nil.
|
|
||||||
),
|
|
||||||
func() float64 {
|
|
||||||
return float64(runtime.NumGoroutine())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
prometheus.MustRegister(m)
|
|
||||||
}
|
|
|
@ -14,15 +14,16 @@
|
||||||
package prometheus_test
|
package prometheus_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"strings"
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
@ -49,10 +50,10 @@ func ExampleGauge() {
|
||||||
func ExampleGaugeVec() {
|
func ExampleGaugeVec() {
|
||||||
opsQueued := prometheus.NewGaugeVec(
|
opsQueued := prometheus.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: "our_company",
|
Namespace: "our_company",
|
||||||
Subsystem: "blob_storage",
|
Subsystem: "blob_storage",
|
||||||
Name: "ops_queued",
|
Name: "ops_queued",
|
||||||
Help: "Number of blob storage operations waiting to be processed, partitioned by user and type.",
|
Help: "Number of blob storage operations waiting to be processed, partitioned by user and type.",
|
||||||
},
|
},
|
||||||
[]string{
|
[]string{
|
||||||
// Which user has requested the operation?
|
// Which user has requested the operation?
|
||||||
|
@ -122,8 +123,8 @@ func ExampleCounter() {
|
||||||
func ExampleCounterVec() {
|
func ExampleCounterVec() {
|
||||||
httpReqs := prometheus.NewCounterVec(
|
httpReqs := prometheus.NewCounterVec(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "http_requests_total",
|
Name: "http_requests_total",
|
||||||
Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
|
Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
|
||||||
},
|
},
|
||||||
[]string{"code", "method"},
|
[]string{"code", "method"},
|
||||||
)
|
)
|
||||||
|
@ -387,89 +388,90 @@ func ExampleSummaryVec() {
|
||||||
temps.WithLabelValues("leiopelma-hochstetteri")
|
temps.WithLabelValues("leiopelma-hochstetteri")
|
||||||
|
|
||||||
// Just for demonstration, let's check the state of the summary vector
|
// Just for demonstration, let's check the state of the summary vector
|
||||||
// by (ab)using its Collect method and the Write method of its elements
|
// by registering it with a custom registry and then let it collect the
|
||||||
// (which is usually only used by Prometheus internally - code like the
|
// metrics.
|
||||||
// following will never appear in your own code).
|
reg := prometheus.NewRegistry()
|
||||||
metricChan := make(chan prometheus.Metric)
|
reg.MustRegister(temps)
|
||||||
go func() {
|
|
||||||
defer close(metricChan)
|
|
||||||
temps.Collect(metricChan)
|
|
||||||
}()
|
|
||||||
|
|
||||||
metricStrings := []string{}
|
metricFamilies, err := reg.Gather()
|
||||||
for metric := range metricChan {
|
if err != nil || len(metricFamilies) != 1 {
|
||||||
dtoMetric := &dto.Metric{}
|
panic("unexpected behavior of custom test registry")
|
||||||
metric.Write(dtoMetric)
|
|
||||||
metricStrings = append(metricStrings, proto.MarshalTextString(dtoMetric))
|
|
||||||
}
|
}
|
||||||
sort.Strings(metricStrings) // For reproducible print order.
|
fmt.Println(proto.MarshalTextString(metricFamilies[0]))
|
||||||
fmt.Println(metricStrings)
|
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// [label: <
|
// name: "pond_temperature_celsius"
|
||||||
// name: "species"
|
// help: "The temperature of the frog pond."
|
||||||
// value: "leiopelma-hochstetteri"
|
// type: SUMMARY
|
||||||
// >
|
// metric: <
|
||||||
// summary: <
|
// label: <
|
||||||
// sample_count: 0
|
// name: "species"
|
||||||
// sample_sum: 0
|
// value: "leiopelma-hochstetteri"
|
||||||
// quantile: <
|
|
||||||
// quantile: 0.5
|
|
||||||
// value: nan
|
|
||||||
// >
|
// >
|
||||||
// quantile: <
|
// summary: <
|
||||||
// quantile: 0.9
|
// sample_count: 0
|
||||||
// value: nan
|
// sample_sum: 0
|
||||||
// >
|
// quantile: <
|
||||||
// quantile: <
|
// quantile: 0.5
|
||||||
// quantile: 0.99
|
// value: nan
|
||||||
// value: nan
|
// >
|
||||||
|
// quantile: <
|
||||||
|
// quantile: 0.9
|
||||||
|
// value: nan
|
||||||
|
// >
|
||||||
|
// quantile: <
|
||||||
|
// quantile: 0.99
|
||||||
|
// value: nan
|
||||||
|
// >
|
||||||
// >
|
// >
|
||||||
// >
|
// >
|
||||||
// label: <
|
// metric: <
|
||||||
// name: "species"
|
// label: <
|
||||||
// value: "lithobates-catesbeianus"
|
// name: "species"
|
||||||
// >
|
// value: "lithobates-catesbeianus"
|
||||||
// summary: <
|
|
||||||
// sample_count: 1000
|
|
||||||
// sample_sum: 31956.100000000017
|
|
||||||
// quantile: <
|
|
||||||
// quantile: 0.5
|
|
||||||
// value: 32.4
|
|
||||||
// >
|
// >
|
||||||
// quantile: <
|
// summary: <
|
||||||
// quantile: 0.9
|
// sample_count: 1000
|
||||||
// value: 41.4
|
// sample_sum: 31956.100000000017
|
||||||
// >
|
// quantile: <
|
||||||
// quantile: <
|
// quantile: 0.5
|
||||||
// quantile: 0.99
|
// value: 32.4
|
||||||
// value: 41.9
|
// >
|
||||||
|
// quantile: <
|
||||||
|
// quantile: 0.9
|
||||||
|
// value: 41.4
|
||||||
|
// >
|
||||||
|
// quantile: <
|
||||||
|
// quantile: 0.99
|
||||||
|
// value: 41.9
|
||||||
|
// >
|
||||||
// >
|
// >
|
||||||
// >
|
// >
|
||||||
// label: <
|
// metric: <
|
||||||
// name: "species"
|
// label: <
|
||||||
// value: "litoria-caerulea"
|
// name: "species"
|
||||||
// >
|
// value: "litoria-caerulea"
|
||||||
// summary: <
|
|
||||||
// sample_count: 1000
|
|
||||||
// sample_sum: 29969.50000000001
|
|
||||||
// quantile: <
|
|
||||||
// quantile: 0.5
|
|
||||||
// value: 31.1
|
|
||||||
// >
|
// >
|
||||||
// quantile: <
|
// summary: <
|
||||||
// quantile: 0.9
|
// sample_count: 1000
|
||||||
// value: 41.3
|
// sample_sum: 29969.50000000001
|
||||||
// >
|
// quantile: <
|
||||||
// quantile: <
|
// quantile: 0.5
|
||||||
// quantile: 0.99
|
// value: 31.1
|
||||||
// value: 41.9
|
// >
|
||||||
|
// quantile: <
|
||||||
|
// quantile: 0.9
|
||||||
|
// value: 41.3
|
||||||
|
// >
|
||||||
|
// quantile: <
|
||||||
|
// quantile: 0.99
|
||||||
|
// value: 41.9
|
||||||
|
// >
|
||||||
// >
|
// >
|
||||||
// >
|
// >
|
||||||
// ]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleConstSummary() {
|
func ExampleNewConstSummary() {
|
||||||
desc := prometheus.NewDesc(
|
desc := prometheus.NewDesc(
|
||||||
"http_request_duration_seconds",
|
"http_request_duration_seconds",
|
||||||
"A summary of the HTTP request durations.",
|
"A summary of the HTTP request durations.",
|
||||||
|
@ -565,7 +567,7 @@ func ExampleHistogram() {
|
||||||
// >
|
// >
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleConstHistogram() {
|
func ExampleNewConstHistogram() {
|
||||||
desc := prometheus.NewDesc(
|
desc := prometheus.NewDesc(
|
||||||
"http_request_duration_seconds",
|
"http_request_duration_seconds",
|
||||||
"A histogram of the HTTP request durations.",
|
"A histogram of the HTTP request durations.",
|
||||||
|
@ -623,18 +625,127 @@ func ExampleConstHistogram() {
|
||||||
// >
|
// >
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExamplePushCollectors() {
|
func ExampleAlreadyRegisteredError() {
|
||||||
hostname, _ := os.Hostname()
|
reqCounter := prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
completionTime := prometheus.NewGauge(prometheus.GaugeOpts{
|
Name: "requests_total",
|
||||||
Name: "db_backup_last_completion_time",
|
Help: "The total number of requests served.",
|
||||||
Help: "The timestamp of the last succesful completion of a DB backup.",
|
|
||||||
})
|
})
|
||||||
completionTime.Set(float64(time.Now().Unix()))
|
if err := prometheus.Register(reqCounter); err != nil {
|
||||||
if err := prometheus.PushCollectors(
|
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
||||||
"db_backup", hostname,
|
// A counter for that metric has been registered before.
|
||||||
"http://pushgateway:9091",
|
// Use the old counter from now on.
|
||||||
completionTime,
|
reqCounter = are.ExistingCollector.(prometheus.Counter)
|
||||||
); err != nil {
|
} else {
|
||||||
fmt.Println("Could not push completion time to Pushgateway:", err)
|
// Something else went wrong!
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleGatherers() {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
temp := prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "temperature_kelvin",
|
||||||
|
Help: "Temperature in Kelvin.",
|
||||||
|
},
|
||||||
|
[]string{"location"},
|
||||||
|
)
|
||||||
|
reg.MustRegister(temp)
|
||||||
|
temp.WithLabelValues("outside").Set(273.14)
|
||||||
|
temp.WithLabelValues("inside").Set(298.44)
|
||||||
|
|
||||||
|
var parser expfmt.TextParser
|
||||||
|
|
||||||
|
text := `
|
||||||
|
# TYPE humidity_percent gauge
|
||||||
|
# HELP humidity_percent Humidity in %.
|
||||||
|
humidity_percent{location="outside"} 45.4
|
||||||
|
humidity_percent{location="inside"} 33.2
|
||||||
|
# TYPE temperature_kelvin gauge
|
||||||
|
# HELP temperature_kelvin Temperature in Kelvin.
|
||||||
|
temperature_kelvin{location="somewhere else"} 4.5
|
||||||
|
`
|
||||||
|
|
||||||
|
parseText := func() ([]*dto.MetricFamily, error) {
|
||||||
|
parsed, err := parser.TextToMetricFamilies(strings.NewReader(text))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var result []*dto.MetricFamily
|
||||||
|
for _, mf := range parsed {
|
||||||
|
result = append(result, mf)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gatherers := prometheus.Gatherers{
|
||||||
|
reg,
|
||||||
|
prometheus.GathererFunc(parseText),
|
||||||
|
}
|
||||||
|
|
||||||
|
gathering, err := gatherers.Gather()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
for _, mf := range gathering {
|
||||||
|
if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Print(out.String())
|
||||||
|
fmt.Println("----------")
|
||||||
|
|
||||||
|
// Note how the temperature_kelvin metric family has been merged from
|
||||||
|
// different sources. Now try
|
||||||
|
text = `
|
||||||
|
# TYPE humidity_percent gauge
|
||||||
|
# HELP humidity_percent Humidity in %.
|
||||||
|
humidity_percent{location="outside"} 45.4
|
||||||
|
humidity_percent{location="inside"} 33.2
|
||||||
|
# TYPE temperature_kelvin gauge
|
||||||
|
# HELP temperature_kelvin Temperature in Kelvin.
|
||||||
|
# Duplicate metric:
|
||||||
|
temperature_kelvin{location="outside"} 265.3
|
||||||
|
# Wrong labels:
|
||||||
|
temperature_kelvin 4.5
|
||||||
|
`
|
||||||
|
|
||||||
|
gathering, err = gatherers.Gather()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
// Note that still as many metrics as possible are returned:
|
||||||
|
out.Reset()
|
||||||
|
for _, mf := range gathering {
|
||||||
|
if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Print(out.String())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// # HELP humidity_percent Humidity in %.
|
||||||
|
// # TYPE humidity_percent gauge
|
||||||
|
// humidity_percent{location="inside"} 33.2
|
||||||
|
// humidity_percent{location="outside"} 45.4
|
||||||
|
// # HELP temperature_kelvin Temperature in Kelvin.
|
||||||
|
// # TYPE temperature_kelvin gauge
|
||||||
|
// temperature_kelvin{location="inside"} 298.44
|
||||||
|
// temperature_kelvin{location="outside"} 273.14
|
||||||
|
// temperature_kelvin{location="somewhere else"} 4.5
|
||||||
|
// ----------
|
||||||
|
// 2 error(s) occurred:
|
||||||
|
// * collected metric temperature_kelvin label:<name:"location" value:"outside" > gauge:<value:265.3 > was collected before with the same name and label values
|
||||||
|
// * collected metric temperature_kelvin gauge:<value:4.5 > has label dimensions inconsistent with previously collected metrics in the same metric family
|
||||||
|
// # HELP humidity_percent Humidity in %.
|
||||||
|
// # TYPE humidity_percent gauge
|
||||||
|
// humidity_percent{location="inside"} 33.2
|
||||||
|
// humidity_percent{location="outside"} 45.4
|
||||||
|
// # HELP temperature_kelvin Temperature in Kelvin.
|
||||||
|
// # TYPE temperature_kelvin gauge
|
||||||
|
// temperature_kelvin{location="inside"} 298.44
|
||||||
|
// temperature_kelvin{location="outside"} 273.14
|
||||||
|
}
|
||||||
|
|
|
@ -18,21 +18,21 @@ import (
|
||||||
"expvar"
|
"expvar"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExpvarCollector collects metrics from the expvar interface. It provides a
|
type expvarCollector struct {
|
||||||
// quick way to expose numeric values that are already exported via expvar as
|
|
||||||
// Prometheus metrics. Note that the data models of expvar and Prometheus are
|
|
||||||
// fundamentally different, and that the ExpvarCollector is inherently
|
|
||||||
// slow. Thus, the ExpvarCollector is probably great for experiments and
|
|
||||||
// prototying, but you should seriously consider a more direct implementation of
|
|
||||||
// Prometheus metrics for monitoring production systems.
|
|
||||||
//
|
|
||||||
// Use NewExpvarCollector to create new instances.
|
|
||||||
type ExpvarCollector struct {
|
|
||||||
exports map[string]*Desc
|
exports map[string]*Desc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExpvarCollector returns a newly allocated ExpvarCollector that still has
|
// NewExpvarCollector returns a newly allocated expvar Collector that still has
|
||||||
// to be registered with the Prometheus registry.
|
// to be registered with a Prometheus registry.
|
||||||
|
//
|
||||||
|
// An expvar Collector collects metrics from the expvar interface. It provides a
|
||||||
|
// quick way to expose numeric values that are already exported via expvar as
|
||||||
|
// Prometheus metrics. Note that the data models of expvar and Prometheus are
|
||||||
|
// fundamentally different, and that the expvar Collector is inherently slower
|
||||||
|
// than native Prometheus metrics. Thus, the expvar Collector is probably great
|
||||||
|
// for experiments and prototying, but you should seriously consider a more
|
||||||
|
// direct implementation of Prometheus metrics for monitoring production
|
||||||
|
// systems.
|
||||||
//
|
//
|
||||||
// The exports map has the following meaning:
|
// The exports map has the following meaning:
|
||||||
//
|
//
|
||||||
|
@ -59,21 +59,21 @@ type ExpvarCollector struct {
|
||||||
// sample values.
|
// sample values.
|
||||||
//
|
//
|
||||||
// Anything that does not fit into the scheme above is silently ignored.
|
// Anything that does not fit into the scheme above is silently ignored.
|
||||||
func NewExpvarCollector(exports map[string]*Desc) *ExpvarCollector {
|
func NewExpvarCollector(exports map[string]*Desc) Collector {
|
||||||
return &ExpvarCollector{
|
return &expvarCollector{
|
||||||
exports: exports,
|
exports: exports,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describe implements Collector.
|
// Describe implements Collector.
|
||||||
func (e *ExpvarCollector) Describe(ch chan<- *Desc) {
|
func (e *expvarCollector) Describe(ch chan<- *Desc) {
|
||||||
for _, desc := range e.exports {
|
for _, desc := range e.exports {
|
||||||
ch <- desc
|
ch <- desc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect implements Collector.
|
// Collect implements Collector.
|
||||||
func (e *ExpvarCollector) Collect(ch chan<- Metric) {
|
func (e *expvarCollector) Collect(ch chan<- Metric) {
|
||||||
for name, desc := range e.exports {
|
for name, desc := range e.exports {
|
||||||
var m Metric
|
var m Metric
|
||||||
expVar := expvar.Get(name)
|
expVar := expvar.Get(name)
|
|
@ -17,7 +17,7 @@ type goCollector struct {
|
||||||
|
|
||||||
// NewGoCollector returns a collector which exports metrics about the current
|
// NewGoCollector returns a collector which exports metrics about the current
|
||||||
// go process.
|
// go process.
|
||||||
func NewGoCollector() *goCollector {
|
func NewGoCollector() Collector {
|
||||||
return &goCollector{
|
return &goCollector{
|
||||||
goroutines: NewGauge(GaugeOpts{
|
goroutines: NewGauge(GaugeOpts{
|
||||||
Namespace: "go",
|
Namespace: "go",
|
||||||
|
|
|
@ -51,11 +51,11 @@ type Histogram interface {
|
||||||
// bucket of a histogram ("le" -> "less or equal").
|
// bucket of a histogram ("le" -> "less or equal").
|
||||||
const bucketLabel = "le"
|
const bucketLabel = "le"
|
||||||
|
|
||||||
|
// DefBuckets are the default Histogram buckets. The default buckets are
|
||||||
|
// tailored to broadly measure the response time (in seconds) of a network
|
||||||
|
// service. Most likely, however, you will be required to define buckets
|
||||||
|
// customized to your use case.
|
||||||
var (
|
var (
|
||||||
// DefBuckets are the default Histogram buckets. The default buckets are
|
|
||||||
// tailored to broadly measure the response time (in seconds) of a
|
|
||||||
// network service. Most likely, however, you will be required to define
|
|
||||||
// buckets customized to your use case.
|
|
||||||
DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
|
DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
|
||||||
|
|
||||||
errBucketLabelNotAllowed = fmt.Errorf(
|
errBucketLabelNotAllowed = fmt.Errorf(
|
||||||
|
@ -210,7 +210,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
|
||||||
// Finally we know the final length of h.upperBounds and can make counts.
|
// Finally we know the final length of h.upperBounds and can make counts.
|
||||||
h.counts = make([]uint64, len(h.upperBounds))
|
h.counts = make([]uint64, len(h.upperBounds))
|
||||||
|
|
||||||
h.Init(h) // Init self-collection.
|
h.init(h) // Init self-collection.
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ type histogram struct {
|
||||||
sumBits uint64
|
sumBits uint64
|
||||||
count uint64
|
count uint64
|
||||||
|
|
||||||
SelfCollector
|
selfCollector
|
||||||
// Note that there is no mutex required.
|
// Note that there is no mutex required.
|
||||||
|
|
||||||
desc *Desc
|
desc *Desc
|
||||||
|
|
|
@ -15,14 +15,114 @@ package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(beorn7): Remove this whole file. It is a partial mirror of
|
||||||
|
// promhttp/http.go (to avoid circular import chains) where everything HTTP
|
||||||
|
// related should live. The functions here are just for avoiding
|
||||||
|
// breakage. Everything is deprecated.
|
||||||
|
|
||||||
|
const (
|
||||||
|
contentTypeHeader = "Content-Type"
|
||||||
|
contentLengthHeader = "Content-Length"
|
||||||
|
contentEncodingHeader = "Content-Encoding"
|
||||||
|
acceptEncodingHeader = "Accept-Encoding"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufPool sync.Pool
|
||||||
|
|
||||||
|
func getBuf() *bytes.Buffer {
|
||||||
|
buf := bufPool.Get()
|
||||||
|
if buf == nil {
|
||||||
|
return &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
return buf.(*bytes.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func giveBuf(buf *bytes.Buffer) {
|
||||||
|
buf.Reset()
|
||||||
|
bufPool.Put(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler returns an HTTP handler for the DefaultGatherer. It is
|
||||||
|
// already instrumented with InstrumentHandler (using "prometheus" as handler
|
||||||
|
// name).
|
||||||
|
//
|
||||||
|
// 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).
|
||||||
|
func Handler() http.Handler {
|
||||||
|
return InstrumentHandler("prometheus", UninstrumentedHandler())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UninstrumentedHandler returns an HTTP handler for the DefaultGatherer.
|
||||||
|
//
|
||||||
|
// Deprecated: Use promhttp.Handler instead. See there for further documentation.
|
||||||
|
func UninstrumentedHandler() http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
mfs, err := DefaultGatherer.Gather()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := expfmt.Negotiate(req.Header)
|
||||||
|
buf := getBuf()
|
||||||
|
defer giveBuf(buf)
|
||||||
|
writer, encoding := decorateWriter(req, buf)
|
||||||
|
enc := expfmt.NewEncoder(writer, contentType)
|
||||||
|
var lastErr error
|
||||||
|
for _, mf := range mfs {
|
||||||
|
if err := enc.Encode(mf); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if closer, ok := writer.(io.Closer); ok {
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
if lastErr != nil && buf.Len() == 0 {
|
||||||
|
http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
header := w.Header()
|
||||||
|
header.Set(contentTypeHeader, string(contentType))
|
||||||
|
header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
|
||||||
|
if encoding != "" {
|
||||||
|
header.Set(contentEncodingHeader, encoding)
|
||||||
|
}
|
||||||
|
w.Write(buf.Bytes())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// decorateWriter wraps a writer to handle gzip compression if requested. It
|
||||||
|
// returns the decorated writer and the appropriate "Content-Encoding" header
|
||||||
|
// (which is empty if no compression is enabled).
|
||||||
|
func decorateWriter(request *http.Request, writer io.Writer) (io.Writer, string) {
|
||||||
|
header := request.Header.Get(acceptEncodingHeader)
|
||||||
|
parts := strings.Split(header, ",")
|
||||||
|
for _, part := range parts {
|
||||||
|
part := strings.TrimSpace(part)
|
||||||
|
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
|
||||||
|
return gzip.NewWriter(writer), "gzip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return writer, ""
|
||||||
|
}
|
||||||
|
|
||||||
var instLabels = []string{"method", "code"}
|
var instLabels = []string{"method", "code"}
|
||||||
|
|
||||||
type nower interface {
|
type nower interface {
|
||||||
|
@ -58,7 +158,7 @@ func nowSeries(t ...time.Time) nower {
|
||||||
// value. http_requests_total is a metric vector partitioned by HTTP method
|
// value. http_requests_total is a metric vector partitioned by HTTP method
|
||||||
// (label name "method") and HTTP status code (label name "code").
|
// (label name "method") and HTTP status code (label name "code").
|
||||||
//
|
//
|
||||||
// Note that InstrumentHandler has several issues:
|
// Deprecated: InstrumentHandler has several issues:
|
||||||
//
|
//
|
||||||
// - It uses Summaries rather than Histograms. Summaries are not useful if
|
// - It uses Summaries rather than Histograms. Summaries are not useful if
|
||||||
// aggregation across multiple instances is required.
|
// aggregation across multiple instances is required.
|
||||||
|
@ -73,8 +173,8 @@ func nowSeries(t ...time.Time) nower {
|
||||||
// performing such writes.
|
// performing such writes.
|
||||||
//
|
//
|
||||||
// Upcoming versions of this package will provide ways of instrumenting HTTP
|
// Upcoming versions of this package will provide ways of instrumenting HTTP
|
||||||
// handlers that are more flexible and have fewer issues. Consider this function
|
// handlers that are more flexible and have fewer issues. Please prefer direct
|
||||||
// DEPRECATED and prefer direct instrumentation in the meantime.
|
// instrumentation in the meantime.
|
||||||
func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
|
func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
|
||||||
return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
|
return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
|
||||||
}
|
}
|
||||||
|
@ -82,6 +182,9 @@ func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFun
|
||||||
// InstrumentHandlerFunc wraps the given function for instrumentation. It
|
// InstrumentHandlerFunc wraps the given function for instrumentation. It
|
||||||
// otherwise works in the same way as InstrumentHandler (and shares the same
|
// otherwise works in the same way as InstrumentHandler (and shares the same
|
||||||
// issues).
|
// issues).
|
||||||
|
//
|
||||||
|
// Deprecated: InstrumentHandlerFunc is deprecated for the same reasons as
|
||||||
|
// InstrumentHandler is.
|
||||||
func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||||
return InstrumentHandlerFuncWithOpts(
|
return InstrumentHandlerFuncWithOpts(
|
||||||
SummaryOpts{
|
SummaryOpts{
|
||||||
|
@ -117,6 +220,9 @@ func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWri
|
||||||
// cannot use SummaryOpts. Instead, a CounterOpts struct is created internally,
|
// cannot use SummaryOpts. Instead, a CounterOpts struct is created internally,
|
||||||
// and all its fields are set to the equally named fields in the provided
|
// and all its fields are set to the equally named fields in the provided
|
||||||
// SummaryOpts.
|
// SummaryOpts.
|
||||||
|
//
|
||||||
|
// Deprecated: InstrumentHandlerWithOpts is deprecated for the same reasons as
|
||||||
|
// InstrumentHandler is.
|
||||||
func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
|
func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
|
||||||
return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
|
return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
|
||||||
}
|
}
|
||||||
|
@ -125,6 +231,9 @@ func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.Hand
|
||||||
// the same issues) but provides more flexibility (at the cost of a more complex
|
// the same issues) but provides more flexibility (at the cost of a more complex
|
||||||
// call syntax). See InstrumentHandlerWithOpts for details how the provided
|
// call syntax). See InstrumentHandlerWithOpts for details how the provided
|
||||||
// SummaryOpts are used.
|
// SummaryOpts are used.
|
||||||
|
//
|
||||||
|
// Deprecated: InstrumentHandlerFuncWithOpts is deprecated for the same reasons
|
||||||
|
// as InstrumentHandler is.
|
||||||
func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||||
reqCnt := NewCounterVec(
|
reqCnt := NewCounterVec(
|
||||||
CounterOpts{
|
CounterOpts{
|
||||||
|
|
|
@ -22,10 +22,8 @@ import (
|
||||||
const separatorByte byte = 255
|
const separatorByte byte = 255
|
||||||
|
|
||||||
// A Metric models a single sample value with its meta data being exported to
|
// A Metric models a single sample value with its meta data being exported to
|
||||||
// Prometheus. Implementers of Metric in this package inclued Gauge, Counter,
|
// Prometheus. Implementations of Metric in this package are Gauge, Counter,
|
||||||
// Untyped, and Summary. Users can implement their own Metric types, but that
|
// Histogram, Summary, and Untyped.
|
||||||
// should be rarely needed. See the example for SelfCollector, which is also an
|
|
||||||
// example for a user-implemented Metric.
|
|
||||||
type Metric interface {
|
type Metric interface {
|
||||||
// Desc returns the descriptor for the Metric. This method idempotently
|
// Desc returns the descriptor for the Metric. This method idempotently
|
||||||
// returns the same descriptor throughout the lifetime of the
|
// returns the same descriptor throughout the lifetime of the
|
||||||
|
@ -36,21 +34,23 @@ type Metric interface {
|
||||||
// Write encodes the Metric into a "Metric" Protocol Buffer data
|
// Write encodes the Metric into a "Metric" Protocol Buffer data
|
||||||
// transmission object.
|
// transmission object.
|
||||||
//
|
//
|
||||||
// Implementers of custom Metric types must observe concurrency safety
|
// Metric implementations must observe concurrency safety as reads of
|
||||||
// as reads of this metric may occur at any time, and any blocking
|
// this metric may occur at any time, and any blocking occurs at the
|
||||||
// occurs at the expense of total performance of rendering all
|
// expense of total performance of rendering all registered
|
||||||
// registered metrics. Ideally Metric implementations should support
|
// metrics. Ideally, Metric implementations should support concurrent
|
||||||
// concurrent readers.
|
// readers.
|
||||||
//
|
//
|
||||||
// The Prometheus client library attempts to minimize memory allocations
|
// While populating dto.Metric, it is the responsibility of the
|
||||||
// and will provide a pre-existing reset dto.Metric pointer. Prometheus
|
// implementation to ensure validity of the Metric protobuf (like valid
|
||||||
// may recycle the dto.Metric proto message, so Metric implementations
|
// UTF-8 strings or syntactically valid metric and label names). It is
|
||||||
// should just populate the provided dto.Metric and then should not keep
|
// recommended to sort labels lexicographically. (Implementers may find
|
||||||
// any reference to it.
|
// LabelPairSorter useful for that.) Callers of Write should still make
|
||||||
//
|
// sure of sorting if they depend on it.
|
||||||
// While populating dto.Metric, labels must be sorted lexicographically.
|
|
||||||
// (Implementers may find LabelPairSorter useful for that.)
|
|
||||||
Write(*dto.Metric) error
|
Write(*dto.Metric) error
|
||||||
|
// TODO(beorn7): The original rationale of passing in a pre-allocated
|
||||||
|
// dto.Metric protobuf to save allocations has disappeared. The
|
||||||
|
// signature of this method should be changed to "Write() (*dto.Metric,
|
||||||
|
// error)".
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opts bundles the options for creating most Metric types. Each metric
|
// Opts bundles the options for creating most Metric types. Each metric
|
||||||
|
|
|
@ -28,7 +28,7 @@ type processCollector struct {
|
||||||
// NewProcessCollector returns a collector which exports the current state of
|
// NewProcessCollector returns a collector which exports the current state of
|
||||||
// process metrics including cpu, memory and file descriptor usage as well as
|
// process metrics including cpu, memory and file descriptor usage as well as
|
||||||
// the process start time for the given process id under the given namespace.
|
// the process start time for the given process id under the given namespace.
|
||||||
func NewProcessCollector(pid int, namespace string) *processCollector {
|
func NewProcessCollector(pid int, namespace string) Collector {
|
||||||
return NewProcessCollectorPIDFn(
|
return NewProcessCollectorPIDFn(
|
||||||
func() (int, error) { return pid, nil },
|
func() (int, error) { return pid, nil },
|
||||||
namespace,
|
namespace,
|
||||||
|
@ -43,7 +43,7 @@ func NewProcessCollector(pid int, namespace string) *processCollector {
|
||||||
func NewProcessCollectorPIDFn(
|
func NewProcessCollectorPIDFn(
|
||||||
pidFn func() (int, error),
|
pidFn func() (int, error),
|
||||||
namespace string,
|
namespace string,
|
||||||
) *processCollector {
|
) Collector {
|
||||||
c := processCollector{
|
c := processCollector{
|
||||||
pidFn: pidFn,
|
pidFn: pidFn,
|
||||||
collectFn: func(chan<- Metric) {},
|
collectFn: func(chan<- Metric) {},
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"bytes"
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
"github.com/prometheus/procfs"
|
"github.com/prometheus/procfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,21 +15,26 @@ func TestProcessCollector(t *testing.T) {
|
||||||
t.Skipf("skipping TestProcessCollector, procfs not available: %s", err)
|
t.Skipf("skipping TestProcessCollector, procfs not available: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
registry := newRegistry()
|
registry := NewRegistry()
|
||||||
registry.Register(NewProcessCollector(os.Getpid(), ""))
|
if err := registry.Register(NewProcessCollector(os.Getpid(), "")); err != nil {
|
||||||
registry.Register(NewProcessCollectorPIDFn(
|
t.Fatal(err)
|
||||||
func() (int, error) { return os.Getpid(), nil }, "foobar"))
|
}
|
||||||
|
if err := registry.Register(NewProcessCollectorPIDFn(
|
||||||
|
func() (int, error) { return os.Getpid(), nil }, "foobar"),
|
||||||
|
); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
s := httptest.NewServer(InstrumentHandler("prometheus", registry))
|
mfs, err := registry.Gather()
|
||||||
defer s.Close()
|
|
||||||
r, err := http.Get(s.URL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer r.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
var buf bytes.Buffer
|
||||||
if err != nil {
|
for _, mf := range mfs {
|
||||||
t.Fatal(err)
|
if _, err := expfmt.MetricFamilyToText(&buf, mf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, re := range []*regexp.Regexp{
|
for _, re := range []*regexp.Regexp{
|
||||||
|
@ -47,8 +51,8 @@ func TestProcessCollector(t *testing.T) {
|
||||||
regexp.MustCompile("foobar_process_resident_memory_bytes [1-9]"),
|
regexp.MustCompile("foobar_process_resident_memory_bytes [1-9]"),
|
||||||
regexp.MustCompile("foobar_process_start_time_seconds [0-9.]{10,}"),
|
regexp.MustCompile("foobar_process_start_time_seconds [0-9.]{10,}"),
|
||||||
} {
|
} {
|
||||||
if !re.Match(body) {
|
if !re.Match(buf.Bytes()) {
|
||||||
t.Errorf("want body to match %s\n%s", re, body)
|
t.Errorf("want body to match %s\n%s", re, buf.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Copyright (c) 2013, The Prometheus Authors
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
// Package promhttp contains functions to create http.Handler instances to
|
||||||
|
// expose Prometheus metrics via HTTP. In later versions of this package, it
|
||||||
|
// will also contain tooling to instrument instances of http.Handler and
|
||||||
|
// http.RoundTripper.
|
||||||
|
//
|
||||||
|
// promhttp.Handler acts on the prometheus.DefaultGatherer. With HandlerFor,
|
||||||
|
// you can create a handler for a custom registry or anything that implements
|
||||||
|
// the Gatherer interface. It also allows to create handlers that act
|
||||||
|
// differently on errors or allow to log errors.
|
||||||
|
package promhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
contentTypeHeader = "Content-Type"
|
||||||
|
contentLengthHeader = "Content-Length"
|
||||||
|
contentEncodingHeader = "Content-Encoding"
|
||||||
|
acceptEncodingHeader = "Accept-Encoding"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufPool sync.Pool
|
||||||
|
|
||||||
|
func getBuf() *bytes.Buffer {
|
||||||
|
buf := bufPool.Get()
|
||||||
|
if buf == nil {
|
||||||
|
return &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
return buf.(*bytes.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func giveBuf(buf *bytes.Buffer) {
|
||||||
|
buf.Reset()
|
||||||
|
bufPool.Put(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler returns an HTTP handler for the prometheus.DefaultGatherer. The
|
||||||
|
// Handler uses the default HandlerOpts, i.e. report the first error as an HTTP
|
||||||
|
// error, no error logging, and compression if requested by the client.
|
||||||
|
//
|
||||||
|
// If you want to create a Handler for the DefaultGatherer with different
|
||||||
|
// HandlerOpts, create it with HandlerFor with prometheus.DefaultGatherer and
|
||||||
|
// your desired HandlerOpts.
|
||||||
|
func Handler() http.Handler {
|
||||||
|
return HandlerFor(prometheus.DefaultGatherer, HandlerOpts{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFor returns an http.Handler for the provided Gatherer. The behavior
|
||||||
|
// ef the Handler is defined by the provided HandlerOpts.
|
||||||
|
func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
mfs, err := reg.Gather()
|
||||||
|
if err != nil {
|
||||||
|
if opts.ErrorLog != nil {
|
||||||
|
opts.ErrorLog.Println("error gathering metrics:", err)
|
||||||
|
}
|
||||||
|
switch opts.ErrorHandling {
|
||||||
|
case PanicOnError:
|
||||||
|
panic(err)
|
||||||
|
case ContinueOnError:
|
||||||
|
if len(mfs) == 0 {
|
||||||
|
http.Error(w, "No metrics gathered, last error:\n\n"+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case HTTPErrorOnError:
|
||||||
|
http.Error(w, "An error has occurred during metrics gathering:\n\n"+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := expfmt.Negotiate(req.Header)
|
||||||
|
buf := getBuf()
|
||||||
|
defer giveBuf(buf)
|
||||||
|
writer, encoding := decorateWriter(req, buf, opts.DisableCompression)
|
||||||
|
enc := expfmt.NewEncoder(writer, contentType)
|
||||||
|
var lastErr error
|
||||||
|
for _, mf := range mfs {
|
||||||
|
if err := enc.Encode(mf); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
if opts.ErrorLog != nil {
|
||||||
|
opts.ErrorLog.Println("error encoding metric family:", err)
|
||||||
|
}
|
||||||
|
switch opts.ErrorHandling {
|
||||||
|
case PanicOnError:
|
||||||
|
panic(err)
|
||||||
|
case ContinueOnError:
|
||||||
|
// Handled later.
|
||||||
|
case HTTPErrorOnError:
|
||||||
|
http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if closer, ok := writer.(io.Closer); ok {
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
if lastErr != nil && buf.Len() == 0 {
|
||||||
|
http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
header := w.Header()
|
||||||
|
header.Set(contentTypeHeader, string(contentType))
|
||||||
|
header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
|
||||||
|
if encoding != "" {
|
||||||
|
header.Set(contentEncodingHeader, encoding)
|
||||||
|
}
|
||||||
|
w.Write(buf.Bytes())
|
||||||
|
// TODO(beorn7): Consider streaming serving of metrics.
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerErrorHandling defines how a Handler serving metrics will handle
|
||||||
|
// errors.
|
||||||
|
type HandlerErrorHandling int
|
||||||
|
|
||||||
|
// These constants cause handlers serving metrics to behave as described if
|
||||||
|
// errors are encountered.
|
||||||
|
const (
|
||||||
|
// Serve an HTTP status code 500 upon the first error
|
||||||
|
// encountered. Report the error message in the body.
|
||||||
|
HTTPErrorOnError HandlerErrorHandling = iota
|
||||||
|
// Ignore errors and try to serve as many metrics as possible. However,
|
||||||
|
// if no metrics can be served, serve an HTTP status code 500 and the
|
||||||
|
// last error message in the body. Only use this in deliberate "best
|
||||||
|
// effort" metrics collection scenarios. It is recommended to at least
|
||||||
|
// log errors (by providing an ErrorLog in HandlerOpts) to not mask
|
||||||
|
// errors completely.
|
||||||
|
ContinueOnError
|
||||||
|
// Panic upon the first error encountered (useful for "crash only" apps).
|
||||||
|
PanicOnError
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger is the minimal interface HandlerOpts needs for logging. Note that
|
||||||
|
// log.Logger from the standard library implements this interface, and it is
|
||||||
|
// easy to implement by custom loggers, if they don't do so already anyway.
|
||||||
|
type Logger interface {
|
||||||
|
Println(v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerOpts specifies options how to serve metrics via an http.Handler. The
|
||||||
|
// zero value of HandlerOpts is a reasonable default.
|
||||||
|
type HandlerOpts struct {
|
||||||
|
// ErrorLog specifies an optional logger for errors collecting and
|
||||||
|
// serving metrics. If nil, errors are not logged at all.
|
||||||
|
ErrorLog Logger
|
||||||
|
// ErrorHandling defines how errors are handled. Note that errors are
|
||||||
|
// logged regardless of the configured ErrorHandling provided ErrorLog
|
||||||
|
// is not nil.
|
||||||
|
ErrorHandling HandlerErrorHandling
|
||||||
|
// If DisableCompression is true, the handler will never compress the
|
||||||
|
// response, even if requested by the client.
|
||||||
|
DisableCompression bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// decorateWriter wraps a writer to handle gzip compression if requested. It
|
||||||
|
// returns the decorated writer and the appropriate "Content-Encoding" header
|
||||||
|
// (which is empty if no compression is enabled).
|
||||||
|
func decorateWriter(request *http.Request, writer io.Writer, compressionDisabled bool) (io.Writer, string) {
|
||||||
|
if compressionDisabled {
|
||||||
|
return writer, ""
|
||||||
|
}
|
||||||
|
header := request.Header.Get(acceptEncodingHeader)
|
||||||
|
parts := strings.Split(header, ",")
|
||||||
|
for _, part := range parts {
|
||||||
|
part := strings.TrimSpace(part)
|
||||||
|
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
|
||||||
|
return gzip.NewWriter(writer), "gzip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return writer, ""
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Copyright (c) 2013, The Prometheus Authors
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
package promhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorCollector struct{}
|
||||||
|
|
||||||
|
func (e errorCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||||
|
ch <- prometheus.NewDesc("invalid_metric", "not helpful", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errorCollector) Collect(ch chan<- prometheus.Metric) {
|
||||||
|
ch <- prometheus.NewInvalidMetric(
|
||||||
|
prometheus.NewDesc("invalid_metric", "not helpful", nil, nil),
|
||||||
|
errors.New("collect error"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandlerErrorHandling(t *testing.T) {
|
||||||
|
|
||||||
|
// Create a registry that collects a MetricFamily with two elements,
|
||||||
|
// another with one, and reports an error.
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
cnt := prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "the_count",
|
||||||
|
Help: "Ah-ah-ah! Thunder and lightning!",
|
||||||
|
})
|
||||||
|
reg.MustRegister(cnt)
|
||||||
|
|
||||||
|
cntVec := prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "name",
|
||||||
|
Help: "docstring",
|
||||||
|
ConstLabels: prometheus.Labels{"constname": "constvalue"},
|
||||||
|
},
|
||||||
|
[]string{"labelname"},
|
||||||
|
)
|
||||||
|
cntVec.WithLabelValues("val1").Inc()
|
||||||
|
cntVec.WithLabelValues("val2").Inc()
|
||||||
|
reg.MustRegister(cntVec)
|
||||||
|
|
||||||
|
reg.MustRegister(errorCollector{})
|
||||||
|
|
||||||
|
logBuf := &bytes.Buffer{}
|
||||||
|
logger := log.New(logBuf, "", 0)
|
||||||
|
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
request, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
request.Header.Add("Accept", "test/plain")
|
||||||
|
|
||||||
|
errorHandler := HandlerFor(reg, HandlerOpts{
|
||||||
|
ErrorLog: logger,
|
||||||
|
ErrorHandling: HTTPErrorOnError,
|
||||||
|
})
|
||||||
|
continueHandler := HandlerFor(reg, HandlerOpts{
|
||||||
|
ErrorLog: logger,
|
||||||
|
ErrorHandling: ContinueOnError,
|
||||||
|
})
|
||||||
|
panicHandler := HandlerFor(reg, HandlerOpts{
|
||||||
|
ErrorLog: logger,
|
||||||
|
ErrorHandling: PanicOnError,
|
||||||
|
})
|
||||||
|
wantMsg := `error gathering metrics: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error
|
||||||
|
`
|
||||||
|
wantErrorBody := `An error has occurred during metrics gathering:
|
||||||
|
|
||||||
|
error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error
|
||||||
|
`
|
||||||
|
wantOKBody := `# HELP name docstring
|
||||||
|
# TYPE name counter
|
||||||
|
name{constname="constvalue",labelname="val1"} 1
|
||||||
|
name{constname="constvalue",labelname="val2"} 1
|
||||||
|
# HELP the_count Ah-ah-ah! Thunder and lightning!
|
||||||
|
# TYPE the_count counter
|
||||||
|
the_count 0
|
||||||
|
`
|
||||||
|
|
||||||
|
errorHandler.ServeHTTP(writer, request)
|
||||||
|
if got, want := writer.Code, http.StatusInternalServerError; got != want {
|
||||||
|
t.Errorf("got HTTP status code %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
if got := logBuf.String(); got != wantMsg {
|
||||||
|
t.Errorf("got log message:\n%s\nwant log mesage:\n%s\n", got, wantMsg)
|
||||||
|
}
|
||||||
|
if got := writer.Body.String(); got != wantErrorBody {
|
||||||
|
t.Errorf("got body:\n%s\nwant body:\n%s\n", got, wantErrorBody)
|
||||||
|
}
|
||||||
|
logBuf.Reset()
|
||||||
|
writer.Body.Reset()
|
||||||
|
writer.Code = http.StatusOK
|
||||||
|
|
||||||
|
continueHandler.ServeHTTP(writer, request)
|
||||||
|
if got, want := writer.Code, http.StatusOK; got != want {
|
||||||
|
t.Errorf("got HTTP status code %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
if got := logBuf.String(); got != wantMsg {
|
||||||
|
t.Errorf("got log message %q, want %q", got, wantMsg)
|
||||||
|
}
|
||||||
|
if got := writer.Body.String(); got != wantOKBody {
|
||||||
|
t.Errorf("got body %q, want %q", got, wantOKBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err == nil {
|
||||||
|
t.Error("expected panic from panicHandler")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
panicHandler.ServeHTTP(writer, request)
|
||||||
|
}
|
|
@ -1,65 +0,0 @@
|
||||||
// Copyright 2015 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Copyright (c) 2013, The Prometheus Authors
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be found
|
|
||||||
// in the LICENSE file.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
// Push triggers a metric collection by the default registry and pushes all
|
|
||||||
// collected metrics to the Pushgateway specified by url. See the Pushgateway
|
|
||||||
// documentation for detailed implications of the job and instance
|
|
||||||
// parameter. instance can be left empty. You can use just host:port or ip:port
|
|
||||||
// as url, in which case 'http://' is added automatically. You can also include
|
|
||||||
// the schema in the URL. However, do not include the '/metrics/jobs/...' part.
|
|
||||||
//
|
|
||||||
// Note that all previously pushed metrics with the same job and instance will
|
|
||||||
// be replaced with the metrics pushed by this call. (It uses HTTP method 'PUT'
|
|
||||||
// to push to the Pushgateway.)
|
|
||||||
func Push(job, instance, url string) error {
|
|
||||||
return defRegistry.Push(job, instance, url, "PUT")
|
|
||||||
}
|
|
||||||
|
|
||||||
// PushAdd works like Push, but only previously pushed metrics with the same
|
|
||||||
// name (and the same job and instance) will be replaced. (It uses HTTP method
|
|
||||||
// 'POST' to push to the Pushgateway.)
|
|
||||||
func PushAdd(job, instance, url string) error {
|
|
||||||
return defRegistry.Push(job, instance, url, "POST")
|
|
||||||
}
|
|
||||||
|
|
||||||
// PushCollectors works like Push, but it does not collect from the default
|
|
||||||
// registry. Instead, it collects from the provided collectors. It is a
|
|
||||||
// convenient way to push only a few metrics.
|
|
||||||
func PushCollectors(job, instance, url string, collectors ...Collector) error {
|
|
||||||
return pushCollectors(job, instance, url, "PUT", collectors...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PushAddCollectors works like PushAdd, but it does not collect from the
|
|
||||||
// default registry. Instead, it collects from the provided collectors. It is a
|
|
||||||
// convenient way to push only a few metrics.
|
|
||||||
func PushAddCollectors(job, instance, url string, collectors ...Collector) error {
|
|
||||||
return pushCollectors(job, instance, url, "POST", collectors...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pushCollectors(job, instance, url, method string, collectors ...Collector) error {
|
|
||||||
r := newRegistry()
|
|
||||||
for _, collector := range collectors {
|
|
||||||
if _, err := r.Register(collector); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.Push(job, instance, url, method)
|
|
||||||
}
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2016 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package push_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/push"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleCollectors() {
|
||||||
|
completionTime := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "db_backup_last_completion_timestamp_seconds",
|
||||||
|
Help: "The timestamp of the last succesful completion of a DB backup.",
|
||||||
|
})
|
||||||
|
completionTime.Set(float64(time.Now().Unix()))
|
||||||
|
if err := push.Collectors(
|
||||||
|
"db_backup", push.HostnameGroupingKey(),
|
||||||
|
"http://pushgateway:9091",
|
||||||
|
completionTime,
|
||||||
|
); err != nil {
|
||||||
|
fmt.Println("Could not push completion time to Pushgateway:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry() {
|
||||||
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
completionTime := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "db_backup_last_completion_timestamp_seconds",
|
||||||
|
Help: "The timestamp of the last succesful completion of a DB backup.",
|
||||||
|
})
|
||||||
|
registry.MustRegister(completionTime)
|
||||||
|
|
||||||
|
completionTime.Set(float64(time.Now().Unix()))
|
||||||
|
if err := push.FromGatherer(
|
||||||
|
"db_backup", push.HostnameGroupingKey(),
|
||||||
|
"http://pushgateway:9091",
|
||||||
|
registry,
|
||||||
|
); err != nil {
|
||||||
|
fmt.Println("Could not push completion time to Pushgateway:", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
// Copyright 2015 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Copyright (c) 2013, The Prometheus Authors
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
// Package push provides functions to push metrics to a Pushgateway. The metrics
|
||||||
|
// to push are either collected from a provided registry, or from explicitly
|
||||||
|
// listed collectors.
|
||||||
|
//
|
||||||
|
// See the documentation of the Pushgateway to understand the meaning of the
|
||||||
|
// grouping parameters and the differences between push.Registry and
|
||||||
|
// push.Collectors on the one hand and push.AddRegistry and push.AddCollectors
|
||||||
|
// on the other hand: https://github.com/prometheus/pushgateway
|
||||||
|
package push
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const contentTypeHeader = "Content-Type"
|
||||||
|
|
||||||
|
// FromGatherer triggers a metric collection by the provided Gatherer (which is
|
||||||
|
// usually implemented by a prometheus.Registry) and pushes all gathered metrics
|
||||||
|
// to the Pushgateway specified by url, using the provided job name and the
|
||||||
|
// (optional) further grouping labels (the grouping map may be nil). See the
|
||||||
|
// Pushgateway documentation for detailed implications of the job and other
|
||||||
|
// grouping labels. Neither the job name nor any grouping label value may
|
||||||
|
// contain a "/". The metrics pushed must not contain a job label of their own
|
||||||
|
// nor any of the grouping labels.
|
||||||
|
//
|
||||||
|
// You can use just host:port or ip:port as url, in which case 'http://' is
|
||||||
|
// added automatically. You can also include the schema in the URL. However, do
|
||||||
|
// not include the '/metrics/jobs/...' part.
|
||||||
|
//
|
||||||
|
// Note that all previously pushed metrics with the same job and other grouping
|
||||||
|
// labels will be replaced with the metrics pushed by this call. (It uses HTTP
|
||||||
|
// method 'PUT' to push to the Pushgateway.)
|
||||||
|
func FromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
|
||||||
|
return push(job, grouping, url, g, "PUT")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFromGatherer works like FromGatherer, but only previously pushed metrics
|
||||||
|
// with the same name (and the same job and other grouping labels) will be
|
||||||
|
// replaced. (It uses HTTP method 'POST' to push to the Pushgateway.)
|
||||||
|
func AddFromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
|
||||||
|
return push(job, grouping, url, g, "POST")
|
||||||
|
}
|
||||||
|
|
||||||
|
func push(job string, grouping map[string]string, pushURL string, g prometheus.Gatherer, method string) error {
|
||||||
|
if !strings.Contains(pushURL, "://") {
|
||||||
|
pushURL = "http://" + pushURL
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(pushURL, "/") {
|
||||||
|
pushURL = pushURL[:len(pushURL)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(job, "/") {
|
||||||
|
return fmt.Errorf("job contains '/': %s", job)
|
||||||
|
}
|
||||||
|
urlComponents := []string{url.QueryEscape(job)}
|
||||||
|
for ln, lv := range grouping {
|
||||||
|
if !model.LabelNameRE.MatchString(ln) {
|
||||||
|
return fmt.Errorf("grouping label has invalid name: %s", ln)
|
||||||
|
}
|
||||||
|
if strings.Contains(lv, "/") {
|
||||||
|
return fmt.Errorf("value of grouping label %s contains '/': %s", ln, lv)
|
||||||
|
}
|
||||||
|
urlComponents = append(urlComponents, ln, lv)
|
||||||
|
}
|
||||||
|
pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/"))
|
||||||
|
|
||||||
|
mfs, err := g.Gather()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)
|
||||||
|
// Check for pre-existing grouping labels:
|
||||||
|
for _, mf := range mfs {
|
||||||
|
for _, m := range mf.GetMetric() {
|
||||||
|
for _, l := range m.GetLabel() {
|
||||||
|
if l.GetName() == "job" {
|
||||||
|
return fmt.Errorf("pushed metric %s (%s) already contains a job label", mf.GetName(), m)
|
||||||
|
}
|
||||||
|
if _, ok := grouping[l.GetName()]; ok {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"pushed metric %s (%s) already contains grouping label %s",
|
||||||
|
mf.GetName(), m, l.GetName(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enc.Encode(mf)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(method, pushURL, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set(contentTypeHeader, string(expfmt.FmtProtoDelim))
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != 202 {
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
|
||||||
|
return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, pushURL, body)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collectors works like FromGatherer, but it does not use a Gatherer. Instead,
|
||||||
|
// it collects from the provided collectors directly. It is a convenient way to
|
||||||
|
// push only a few metrics.
|
||||||
|
func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
|
||||||
|
return pushCollectors(job, grouping, url, "PUT", collectors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCollectors works like AddFromGatherer, but it does not use a Gatherer.
|
||||||
|
// Instead, it collects from the provided collectors directly. It is a
|
||||||
|
// convenient way to push only a few metrics.
|
||||||
|
func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
|
||||||
|
return pushCollectors(job, grouping, url, "POST", collectors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pushCollectors(job string, grouping map[string]string, url, method string, collectors ...prometheus.Collector) error {
|
||||||
|
r := prometheus.NewRegistry()
|
||||||
|
for _, collector := range collectors {
|
||||||
|
if err := r.Register(collector); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return push(job, grouping, url, r, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HostnameGroupingKey returns a label map with the only entry
|
||||||
|
// {instance="<hostname>"}. This can be conveniently used as the grouping
|
||||||
|
// parameter if metrics should be pushed with the hostname as label. The
|
||||||
|
// returned map is created upon each call so that the caller is free to add more
|
||||||
|
// labels to the map.
|
||||||
|
func HostnameGroupingKey() map[string]string {
|
||||||
|
hostname, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return map[string]string{"instance": "unknown"}
|
||||||
|
}
|
||||||
|
return map[string]string{"instance": hostname}
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Copyright (c) 2013, The Prometheus Authors
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
package push
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPush(t *testing.T) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
lastMethod string
|
||||||
|
lastBody []byte
|
||||||
|
lastPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
host, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake a Pushgateway that always responds with 202.
|
||||||
|
pgwOK := httptest.NewServer(
|
||||||
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
lastMethod = r.Method
|
||||||
|
var err error
|
||||||
|
lastBody, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
lastPath = r.URL.EscapedPath()
|
||||||
|
w.Header().Set("Content-Type", `text/plain; charset=utf-8`)
|
||||||
|
w.WriteHeader(http.StatusAccepted)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
defer pgwOK.Close()
|
||||||
|
|
||||||
|
// Fake a Pushgateway that always responds with 500.
|
||||||
|
pgwErr := httptest.NewServer(
|
||||||
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "fake error", http.StatusInternalServerError)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
defer pgwErr.Close()
|
||||||
|
|
||||||
|
metric1 := prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "testname1",
|
||||||
|
Help: "testhelp1",
|
||||||
|
})
|
||||||
|
metric2 := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "testname2",
|
||||||
|
Help: "testhelp2",
|
||||||
|
ConstLabels: prometheus.Labels{"foo": "bar", "dings": "bums"},
|
||||||
|
})
|
||||||
|
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
reg.MustRegister(metric1)
|
||||||
|
reg.MustRegister(metric2)
|
||||||
|
|
||||||
|
mfs, err := reg.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)
|
||||||
|
|
||||||
|
for _, mf := range mfs {
|
||||||
|
if err := enc.Encode(mf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wantBody := buf.Bytes()
|
||||||
|
|
||||||
|
// PushCollectors, all good.
|
||||||
|
if err := Collectors("testjob", HostnameGroupingKey(), pgwOK.URL, metric1, metric2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if lastMethod != "PUT" {
|
||||||
|
t.Error("want method PUT for PushCollectors, got", lastMethod)
|
||||||
|
}
|
||||||
|
if bytes.Compare(lastBody, wantBody) != 0 {
|
||||||
|
t.Errorf("got body %v, want %v", lastBody, wantBody)
|
||||||
|
}
|
||||||
|
if lastPath != "/metrics/job/testjob/instance/"+host {
|
||||||
|
t.Error("unexpected path:", lastPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushAddCollectors, with nil grouping, all good.
|
||||||
|
if err := AddCollectors("testjob", nil, pgwOK.URL, metric1, metric2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if lastMethod != "POST" {
|
||||||
|
t.Error("want method POST for PushAddCollectors, got", lastMethod)
|
||||||
|
}
|
||||||
|
if bytes.Compare(lastBody, wantBody) != 0 {
|
||||||
|
t.Errorf("got body %v, want %v", lastBody, wantBody)
|
||||||
|
}
|
||||||
|
if lastPath != "/metrics/job/testjob" {
|
||||||
|
t.Error("unexpected path:", lastPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushCollectors with a broken PGW.
|
||||||
|
if err := Collectors("testjob", nil, pgwErr.URL, metric1, metric2); err == nil {
|
||||||
|
t.Error("push to broken Pushgateway succeeded")
|
||||||
|
} else {
|
||||||
|
if got, want := err.Error(), "unexpected status code 500 while pushing to "+pgwErr.URL+"/metrics/job/testjob: fake error\n"; got != want {
|
||||||
|
t.Errorf("got error %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushCollectors with invalid grouping or job.
|
||||||
|
if err := Collectors("testjob", map[string]string{"foo": "bums"}, pgwErr.URL, metric1, metric2); err == nil {
|
||||||
|
t.Error("push with grouping contained in metrics succeeded")
|
||||||
|
}
|
||||||
|
if err := Collectors("test/job", nil, pgwErr.URL, metric1, metric2); err == nil {
|
||||||
|
t.Error("push with invalid job value succeeded")
|
||||||
|
}
|
||||||
|
if err := Collectors("testjob", map[string]string{"foo/bar": "bums"}, pgwErr.URL, metric1, metric2); err == nil {
|
||||||
|
t.Error("push with invalid grouping succeeded")
|
||||||
|
}
|
||||||
|
if err := Collectors("testjob", map[string]string{"foo-bar": "bums"}, pgwErr.URL, metric1, metric2); err == nil {
|
||||||
|
t.Error("push with invalid grouping succeeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push registry, all good.
|
||||||
|
if err := FromGatherer("testjob", HostnameGroupingKey(), pgwOK.URL, reg); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if lastMethod != "PUT" {
|
||||||
|
t.Error("want method PUT for Push, got", lastMethod)
|
||||||
|
}
|
||||||
|
if bytes.Compare(lastBody, wantBody) != 0 {
|
||||||
|
t.Errorf("got body %v, want %v", lastBody, wantBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushAdd registry, all good.
|
||||||
|
if err := AddFromGatherer("testjob", map[string]string{"a": "x", "b": "y"}, pgwOK.URL, reg); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if lastMethod != "POST" {
|
||||||
|
t.Error("want method POSTT for PushAdd, got", lastMethod)
|
||||||
|
}
|
||||||
|
if bytes.Compare(lastBody, wantBody) != 0 {
|
||||||
|
t.Errorf("got body %v, want %v", lastBody, wantBody)
|
||||||
|
}
|
||||||
|
if lastPath != "/metrics/job/testjob/a/x/b/y" && lastPath != "/metrics/job/testjob/b/y/a/x" {
|
||||||
|
t.Error("unexpected path:", lastPath)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -17,41 +17,30 @@
|
||||||
// Use of this source code is governed by a BSD-style license that can be found
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
// in the LICENSE file.
|
// in the LICENSE file.
|
||||||
|
|
||||||
package prometheus
|
package prometheus_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeResponseWriter struct {
|
|
||||||
header http.Header
|
|
||||||
body bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *fakeResponseWriter) Header() http.Header {
|
|
||||||
return r.header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *fakeResponseWriter) Write(d []byte) (l int, err error) {
|
|
||||||
return r.body.Write(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *fakeResponseWriter) WriteHeader(c int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHandler(t testing.TB) {
|
func testHandler(t testing.TB) {
|
||||||
|
|
||||||
metricVec := NewCounterVec(
|
metricVec := prometheus.NewCounterVec(
|
||||||
CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Help: "docstring",
|
Help: "docstring",
|
||||||
ConstLabels: Labels{"constname": "constvalue"},
|
ConstLabels: prometheus.Labels{"constname": "constvalue"},
|
||||||
},
|
},
|
||||||
[]string{"labelname"},
|
[]string{"labelname"},
|
||||||
)
|
)
|
||||||
|
@ -59,8 +48,6 @@ func testHandler(t testing.TB) {
|
||||||
metricVec.WithLabelValues("val1").Inc()
|
metricVec.WithLabelValues("val1").Inc()
|
||||||
metricVec.WithLabelValues("val2").Inc()
|
metricVec.WithLabelValues("val2").Inc()
|
||||||
|
|
||||||
varintBuf := make([]byte, binary.MaxVarintLen32)
|
|
||||||
|
|
||||||
externalMetricFamily := &dto.MetricFamily{
|
externalMetricFamily := &dto.MetricFamily{
|
||||||
Name: proto.String("externalname"),
|
Name: proto.String("externalname"),
|
||||||
Help: proto.String("externaldocstring"),
|
Help: proto.String("externaldocstring"),
|
||||||
|
@ -83,18 +70,9 @@ func testHandler(t testing.TB) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
marshaledExternalMetricFamily, err := proto.Marshal(externalMetricFamily)
|
externalBuf := &bytes.Buffer{}
|
||||||
if err != nil {
|
enc := expfmt.NewEncoder(externalBuf, expfmt.FmtProtoDelim)
|
||||||
t.Fatal(err)
|
if err := enc.Encode(externalMetricFamily); err != nil {
|
||||||
}
|
|
||||||
var externalBuf bytes.Buffer
|
|
||||||
l := binary.PutUvarint(varintBuf, uint64(len(marshaledExternalMetricFamily)))
|
|
||||||
_, err = externalBuf.Write(varintBuf[:l])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = externalBuf.Write(marshaledExternalMetricFamily)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
externalMetricFamilyAsBytes := externalBuf.Bytes()
|
externalMetricFamilyAsBytes := externalBuf.Bytes()
|
||||||
|
@ -160,18 +138,9 @@ metric: <
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
marshaledExpectedMetricFamily, err := proto.Marshal(expectedMetricFamily)
|
buf := &bytes.Buffer{}
|
||||||
if err != nil {
|
enc = expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)
|
||||||
t.Fatal(err)
|
if err := enc.Encode(expectedMetricFamily); err != nil {
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
l = binary.PutUvarint(varintBuf, uint64(len(marshaledExpectedMetricFamily)))
|
|
||||||
_, err = buf.Write(varintBuf[:l])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = buf.Write(marshaledExpectedMetricFamily)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
expectedMetricFamilyAsBytes := buf.Bytes()
|
expectedMetricFamilyAsBytes := buf.Bytes()
|
||||||
|
@ -216,7 +185,7 @@ metric: <
|
||||||
|
|
||||||
externalMetricFamilyWithSameName := &dto.MetricFamily{
|
externalMetricFamilyWithSameName := &dto.MetricFamily{
|
||||||
Name: proto.String("name"),
|
Name: proto.String("name"),
|
||||||
Help: proto.String("inconsistent help string does not matter here"),
|
Help: proto.String("docstring"),
|
||||||
Type: dto.MetricType_COUNTER.Enum(),
|
Type: dto.MetricType_COUNTER.Enum(),
|
||||||
Metric: []*dto.Metric{
|
Metric: []*dto.Metric{
|
||||||
{
|
{
|
||||||
|
@ -248,7 +217,7 @@ metric: <
|
||||||
var scenarios = []struct {
|
var scenarios = []struct {
|
||||||
headers map[string]string
|
headers map[string]string
|
||||||
out output
|
out output
|
||||||
collector Collector
|
collector prometheus.Collector
|
||||||
externalMF []*dto.MetricFamily
|
externalMF []*dto.MetricFamily
|
||||||
}{
|
}{
|
||||||
{ // 0
|
{ // 0
|
||||||
|
@ -485,21 +454,22 @@ metric: <
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, scenario := range scenarios {
|
for i, scenario := range scenarios {
|
||||||
registry := newRegistry()
|
registry := prometheus.NewPedanticRegistry()
|
||||||
registry.collectChecksEnabled = true
|
gatherer := prometheus.Gatherer(registry)
|
||||||
|
if scenario.externalMF != nil {
|
||||||
|
gatherer = prometheus.Gatherers{
|
||||||
|
registry,
|
||||||
|
prometheus.GathererFunc(func() ([]*dto.MetricFamily, error) {
|
||||||
|
return scenario.externalMF, nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if scenario.collector != nil {
|
if scenario.collector != nil {
|
||||||
registry.Register(scenario.collector)
|
registry.Register(scenario.collector)
|
||||||
}
|
}
|
||||||
if scenario.externalMF != nil {
|
writer := httptest.NewRecorder()
|
||||||
registry.metricFamilyInjectionHook = func() []*dto.MetricFamily {
|
handler := prometheus.InstrumentHandler("prometheus", promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}))
|
||||||
return scenario.externalMF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writer := &fakeResponseWriter{
|
|
||||||
header: http.Header{},
|
|
||||||
}
|
|
||||||
handler := InstrumentHandler("prometheus", registry)
|
|
||||||
request, _ := http.NewRequest("GET", "/", nil)
|
request, _ := http.NewRequest("GET", "/", nil)
|
||||||
for key, value := range scenario.headers {
|
for key, value := range scenario.headers {
|
||||||
request.Header.Add(key, value)
|
request.Header.Add(key, value)
|
||||||
|
@ -507,7 +477,7 @@ metric: <
|
||||||
handler(writer, request)
|
handler(writer, request)
|
||||||
|
|
||||||
for key, value := range scenario.out.headers {
|
for key, value := range scenario.out.headers {
|
||||||
if writer.Header().Get(key) != value {
|
if writer.HeaderMap.Get(key) != value {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"%d. expected %q for header %q, got %q",
|
"%d. expected %q for header %q, got %q",
|
||||||
i, value, key, writer.Header().Get(key),
|
i, value, key, writer.Header().Get(key),
|
||||||
|
@ -515,10 +485,10 @@ metric: <
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(scenario.out.body, writer.body.Bytes()) {
|
if !bytes.Equal(scenario.out.body, writer.Body.Bytes()) {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"%d. expected %q for body, got %q",
|
"%d. expected body:\n%s\ngot body:\n%s\n",
|
||||||
i, scenario.out.body, writer.body.Bytes(),
|
i, scenario.out.body, writer.Body.Bytes(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -533,3 +503,43 @@ func BenchmarkHandler(b *testing.B) {
|
||||||
testHandler(b)
|
testHandler(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegisterWithOrGet(t *testing.T) {
|
||||||
|
// Replace the default registerer just to be sure. This is bad, but this
|
||||||
|
// whole test will go away once RegisterOrGet is removed.
|
||||||
|
oldRegisterer := prometheus.DefaultRegisterer
|
||||||
|
defer func() {
|
||||||
|
prometheus.DefaultRegisterer = oldRegisterer
|
||||||
|
}()
|
||||||
|
prometheus.DefaultRegisterer = prometheus.NewRegistry()
|
||||||
|
original := prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "help",
|
||||||
|
},
|
||||||
|
[]string{"foo", "bar"},
|
||||||
|
)
|
||||||
|
equalButNotSame := prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "help",
|
||||||
|
},
|
||||||
|
[]string{"foo", "bar"},
|
||||||
|
)
|
||||||
|
if err := prometheus.Register(original); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := prometheus.Register(equalButNotSame); err == nil {
|
||||||
|
t.Fatal("expected error when registringe equal collector")
|
||||||
|
}
|
||||||
|
existing, err := prometheus.RegisterOrGet(equalButNotSame)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if existing != original {
|
||||||
|
t.Error("expected original collector but got something else")
|
||||||
|
}
|
||||||
|
if existing == equalButNotSame {
|
||||||
|
t.Error("expected original callector but got new one")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -53,8 +53,8 @@ type Summary interface {
|
||||||
Observe(float64)
|
Observe(float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefObjectives are the default Summary quantile values.
|
||||||
var (
|
var (
|
||||||
// DefObjectives are the default Summary quantile values.
|
|
||||||
DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
|
DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
|
||||||
|
|
||||||
errQuantileLabelNotAllowed = fmt.Errorf(
|
errQuantileLabelNotAllowed = fmt.Errorf(
|
||||||
|
@ -139,11 +139,11 @@ type SummaryOpts struct {
|
||||||
BufCap uint32
|
BufCap uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Great fuck-up with the sliding-window decay algorithm... The Merge
|
// Great fuck-up with the sliding-window decay algorithm... The Merge method of
|
||||||
// method of perk/quantile is actually not working as advertised - and it might
|
// perk/quantile is actually not working as advertised - and it might be
|
||||||
// be unfixable, as the underlying algorithm is apparently not capable of
|
// unfixable, as the underlying algorithm is apparently not capable of merging
|
||||||
// merging summaries in the first place. To avoid using Merge, we are currently
|
// summaries in the first place. To avoid using Merge, we are currently adding
|
||||||
// adding observations to _each_ age bucket, i.e. the effort to add a sample is
|
// observations to _each_ age bucket, i.e. the effort to add a sample is
|
||||||
// essentially multiplied by the number of age buckets. When rotating age
|
// essentially multiplied by the number of age buckets. When rotating age
|
||||||
// buckets, we empty the previous head stream. On scrape time, we simply take
|
// buckets, we empty the previous head stream. On scrape time, we simply take
|
||||||
// the quantiles from the head stream (no merging required). Result: More effort
|
// the quantiles from the head stream (no merging required). Result: More effort
|
||||||
|
@ -227,12 +227,12 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
||||||
}
|
}
|
||||||
sort.Float64s(s.sortedObjectives)
|
sort.Float64s(s.sortedObjectives)
|
||||||
|
|
||||||
s.Init(s) // Init self-collection.
|
s.init(s) // Init self-collection.
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
type summary struct {
|
type summary struct {
|
||||||
SelfCollector
|
selfCollector
|
||||||
|
|
||||||
bufMtx sync.Mutex // Protects hotBuf and hotBufExpTime.
|
bufMtx sync.Mutex // Protects hotBuf and hotBufExpTime.
|
||||||
mtx sync.Mutex // Protects every other moving part.
|
mtx sync.Mutex // Protects every other moving part.
|
||||||
|
|
|
@ -329,8 +329,8 @@ func TestSummaryDecay(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBounds(vars []float64, q, ε float64) (min, max float64) {
|
func getBounds(vars []float64, q, ε float64) (min, max float64) {
|
||||||
// TODO: This currently tolerates an error of up to 2*ε. The error must
|
// TODO(beorn7): This currently tolerates an error of up to 2*ε. The
|
||||||
// be at most ε, but for some reason, it's sometimes slightly
|
// error must be at most ε, but for some reason, it's sometimes slightly
|
||||||
// higher. That's a bug.
|
// higher. That's a bug.
|
||||||
n := float64(len(vars))
|
n := float64(len(vars))
|
||||||
lower := int((q - 2*ε) * n)
|
lower := int((q - 2*ε) * n)
|
||||||
|
|
|
@ -48,7 +48,7 @@ type value struct {
|
||||||
// operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
// operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||||
valBits uint64
|
valBits uint64
|
||||||
|
|
||||||
SelfCollector
|
selfCollector
|
||||||
|
|
||||||
desc *Desc
|
desc *Desc
|
||||||
valType ValueType
|
valType ValueType
|
||||||
|
@ -68,7 +68,7 @@ func newValue(desc *Desc, valueType ValueType, val float64, labelValues ...strin
|
||||||
valBits: math.Float64bits(val),
|
valBits: math.Float64bits(val),
|
||||||
labelPairs: makeLabelPairs(desc, labelValues),
|
labelPairs: makeLabelPairs(desc, labelValues),
|
||||||
}
|
}
|
||||||
result.Init(result)
|
result.init(result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ func (v *value) Write(out *dto.Metric) error {
|
||||||
// library to back the implementations of CounterFunc, GaugeFunc, and
|
// library to back the implementations of CounterFunc, GaugeFunc, and
|
||||||
// UntypedFunc.
|
// UntypedFunc.
|
||||||
type valueFunc struct {
|
type valueFunc struct {
|
||||||
SelfCollector
|
selfCollector
|
||||||
|
|
||||||
desc *Desc
|
desc *Desc
|
||||||
valType ValueType
|
valType ValueType
|
||||||
|
@ -134,7 +134,7 @@ func newValueFunc(desc *Desc, valueType ValueType, function func() float64) *val
|
||||||
function: function,
|
function: function,
|
||||||
labelPairs: makeLabelPairs(desc, nil),
|
labelPairs: makeLabelPairs(desc, nil),
|
||||||
}
|
}
|
||||||
result.Init(result)
|
result.init(result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue