Update vendoring of client_golang and friends

Signed-off-by: beorn7 <beorn@soundcloud.com>
This commit is contained in:
beorn7 2018-09-17 13:03:28 +02:00
parent 128ff546b8
commit 5e4a76e66b
20 changed files with 1239 additions and 317 deletions

View file

@ -30,10 +30,10 @@ import (
// DefaultRoundTripper is used if no RoundTripper is set in Config. // DefaultRoundTripper is used if no RoundTripper is set in Config.
var DefaultRoundTripper http.RoundTripper = &http.Transport{ var DefaultRoundTripper http.RoundTripper = &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{ DialContext: (&net.Dialer{
Timeout: 30 * time.Second, Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second, KeepAlive: 30 * time.Second,
}).Dial, }).DialContext,
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
} }

View file

@ -29,27 +29,71 @@ type Collector interface {
// collected by this Collector to the provided channel and returns once // collected by this Collector to the provided channel and returns once
// the last descriptor has been sent. The sent descriptors fulfill the // the last descriptor has been sent. The sent descriptors fulfill the
// consistency and uniqueness requirements described in the Desc // consistency and uniqueness requirements described in the Desc
// documentation. (It is valid if one and the same Collector sends // documentation.
// duplicate descriptors. Those duplicates are simply ignored. However, //
// two different Collectors must not send duplicate descriptors.) This // It is valid if one and the same Collector sends duplicate
// method idempotently sends the same descriptors throughout the // descriptors. Those duplicates are simply ignored. However, two
// lifetime of the Collector. If a Collector encounters an error while // different Collectors must not send duplicate descriptors.
// executing this method, it must send an invalid descriptor (created //
// with NewInvalidDesc) to signal the error to the registry. // Sending no descriptor at all marks the Collector as “unchecked”,
// i.e. no checks will be performed at registration time, and the
// Collector may yield any Metric it sees fit in its Collect method.
//
// This method idempotently sends the same descriptors throughout the
// lifetime of the Collector.
//
// If a Collector encounters an error while executing this method, it
// must send an invalid descriptor (created with NewInvalidDesc) to
// signal the error to the registry.
Describe(chan<- *Desc) Describe(chan<- *Desc)
// Collect is called by the Prometheus registry when collecting // Collect is called by the Prometheus registry when collecting
// metrics. The implementation sends each collected metric via the // metrics. The implementation sends each collected metric via the
// provided channel and returns once the last metric has been sent. The // provided channel and returns once the last metric has been sent. The
// descriptor of each sent metric is one of those returned by // descriptor of each sent metric is one of those returned by Describe
// Describe. Returned metrics that share the same descriptor must differ // (unless the Collector is unchecked, see above). Returned metrics that
// in their variable label values. This method may be called // share the same descriptor must differ in their variable label
// concurrently and must therefore be implemented in a concurrency safe // values.
// way. Blocking occurs at the expense of total performance of rendering //
// all registered metrics. Ideally, Collector implementations support // This method may be called concurrently and must therefore be
// concurrent readers. // implemented in a concurrency safe way. Blocking occurs at the expense
// of total performance of rendering all registered metrics. Ideally,
// Collector implementations support concurrent readers.
Collect(chan<- Metric) Collect(chan<- Metric)
} }
// DescribeByCollect is a helper to implement the Describe method of a custom
// Collector. It collects the metrics from the provided Collector and sends
// their descriptors to the provided channel.
//
// If a Collector collects the same metrics throughout its lifetime, its
// Describe method can simply be implemented as:
//
// func (c customCollector) Describe(ch chan<- *Desc) {
// DescribeByCollect(c, ch)
// }
//
// However, this will not work if the metrics collected change dynamically over
// the lifetime of the Collector in a way that their combined set of descriptors
// changes as well. The shortcut implementation will then violate the contract
// of the Describe method. If a Collector sometimes collects no metrics at all
// (for example vectors like CounterVec, GaugeVec, etc., which only collect
// metrics after a metric with a fully specified label set has been accessed),
// it might even get registered as an unchecked Collecter (cf. the Register
// method of the Registerer interface). Hence, only use this shortcut
// implementation of Describe if you are certain to fulfill the contract.
//
// The Collector example demonstrates a use of DescribeByCollect.
func DescribeByCollect(c Collector, descs chan<- *Desc) {
metrics := make(chan Metric)
go func() {
c.Collect(metrics)
close(metrics)
}()
for m := range metrics {
descs <- m.Desc()
}
}
// selfCollector implements Collector for a single Metric so that the Metric // selfCollector implements Collector for a single Metric so that the Metric
// collects itself. Add it as an anonymous field to a struct that implements // collects itself. Add it as an anonymous field to a struct that implements
// Metric, and call init with the Metric itself as an argument. // Metric, and call init with the Metric itself as an argument.

View file

@ -67,7 +67,7 @@ type Desc struct {
// NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc // NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc
// and will be reported on registration time. variableLabels and constLabels can // and will be reported on registration time. variableLabels and constLabels can
// be nil if no such labels should be set. fqName and help must not be empty. // be nil if no such labels should be set. fqName must not be empty.
// //
// variableLabels only contain the label names. Their label values are variable // variableLabels only contain the label names. Their label values are variable
// and therefore not part of the Desc. (They are managed within the Metric.) // and therefore not part of the Desc. (They are managed within the Metric.)
@ -80,10 +80,6 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
help: help, help: help,
variableLabels: variableLabels, variableLabels: variableLabels,
} }
if help == "" {
d.err = errors.New("empty help string")
return d
}
if !model.IsValidMetricName(model.LabelValue(fqName)) { if !model.IsValidMetricName(model.LabelValue(fqName)) {
d.err = fmt.Errorf("%q is not a valid metric name", fqName) d.err = fmt.Errorf("%q is not a valid metric name", fqName)
return d return d
@ -156,7 +152,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
Value: proto.String(v), Value: proto.String(v),
}) })
} }
sort.Sort(LabelPairSorter(d.constLabelPairs)) sort.Sort(labelPairSorter(d.constLabelPairs))
return d return d
} }

View file

@ -121,7 +121,17 @@
// NewConstSummary (and their respective Must… versions). That will happen in // NewConstSummary (and their respective Must… versions). That will happen in
// the Collect method. The Describe method has to return separate Desc // the Collect method. The Describe method has to return separate Desc
// instances, representative of the “throw-away” metrics to be created later. // instances, representative of the “throw-away” metrics to be created later.
// NewDesc comes in handy to create those Desc instances. // NewDesc comes in handy to create those Desc instances. Alternatively, you
// could return no Desc at all, which will marke the Collector “unchecked”. No
// checks are porformed at registration time, but metric consistency will still
// be ensured at scrape time, i.e. any inconsistencies will lead to scrape
// errors. Thus, with unchecked Collectors, the responsibility to not collect
// metrics that lead to inconsistencies in the total scrape result lies with the
// implementer of the Collector. While this is not a desirable state, it is
// sometimes necessary. The typical use case is a situatios where the exact
// metrics to be returned by a Collector cannot be predicted at registration
// time, but the implementer has sufficient knowledge of the whole system to
// guarantee metric consistency.
// //
// The Collector example illustrates the use case. You can also look at the // The Collector example illustrates the use case. You can also look at the
// source code of the processCollector (mirroring process metrics), the // source code of the processCollector (mirroring process metrics), the

View file

@ -1,3 +1,16 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus package prometheus
// Inline and byte-free variant of hash/fnv's fnv64a. // Inline and byte-free variant of hash/fnv's fnv64a.

View file

@ -1,3 +1,16 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus package prometheus
import ( import (

View file

@ -16,7 +16,9 @@ package prometheus
import ( import (
"fmt" "fmt"
"math" "math"
"runtime"
"sort" "sort"
"sync"
"sync/atomic" "sync/atomic"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
@ -108,8 +110,9 @@ func ExponentialBuckets(start, factor float64, count int) []float64 {
} }
// HistogramOpts bundles the options for creating a Histogram metric. It is // HistogramOpts bundles the options for creating a Histogram metric. It is
// mandatory to set Name and Help to a non-empty string. All other fields are // mandatory to set Name to a non-empty string. All other fields are optional
// optional and can safely be left at their zero value. // and can safely be left at their zero value, although it is strongly
// encouraged to set a Help string.
type HistogramOpts struct { type HistogramOpts struct {
// Namespace, Subsystem, and Name are components of the fully-qualified // Namespace, Subsystem, and Name are components of the fully-qualified
// name of the Histogram (created by joining these components with // name of the Histogram (created by joining these components with
@ -120,7 +123,7 @@ type HistogramOpts struct {
Subsystem string Subsystem string
Name string Name string
// Help provides information about this Histogram. Mandatory! // Help provides information about this Histogram.
// //
// Metrics with the same fully-qualified name must have the same Help // Metrics with the same fully-qualified name must have the same Help
// string. // string.
@ -200,28 +203,49 @@ 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)) // for both states:
h.counts[0].buckets = make([]uint64, len(h.upperBounds))
h.counts[1].buckets = make([]uint64, len(h.upperBounds))
h.init(h) // Init self-collection. h.init(h) // Init self-collection.
return h return h
} }
type histogram struct { type histogramCounts struct {
// sumBits contains the bits of the float64 representing the sum of all // sumBits contains the bits of the float64 representing the sum of all
// observations. sumBits and count have to go first in the struct to // observations. sumBits and count have to go first in the struct to
// guarantee alignment for atomic operations. // guarantee alignment for atomic operations.
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
sumBits uint64 sumBits uint64
count uint64 count uint64
buckets []uint64
}
type histogram struct {
selfCollector selfCollector
// Note that there is no mutex required. desc *Desc
writeMtx sync.Mutex // Only used in the Write method.
desc *Desc
upperBounds []float64 upperBounds []float64
counts []uint64
// Two counts, one is "hot" for lock-free observations, the other is
// "cold" for writing out a dto.Metric.
counts [2]histogramCounts
hotIdx int // Index of currently-hot counts. Only used within Write.
// This is a complicated one. For lock-free yet atomic observations, we
// need to save the total count of observations again, combined with the
// index of the currently-hot counts struct, so that we can perform the
// operation on both values atomically. The least significant bit
// defines the hot counts struct. The remaining 63 bits represent the
// total count of observations. This happens under the assumption that
// the 63bit count will never overflow. Rationale: An observations takes
// about 30ns. Let's assume it could happen in 10ns. Overflowing the
// counter will then take at least (2^63)*10ns, which is about 3000
// years.
countAndHotIdx uint64
labelPairs []*dto.LabelPair labelPairs []*dto.LabelPair
} }
@ -241,36 +265,113 @@ func (h *histogram) Observe(v float64) {
// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op // 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
// 300 buckets: 154 ns/op linear - binary 61.6 ns/op // 300 buckets: 154 ns/op linear - binary 61.6 ns/op
i := sort.SearchFloat64s(h.upperBounds, v) i := sort.SearchFloat64s(h.upperBounds, v)
if i < len(h.counts) {
atomic.AddUint64(&h.counts[i], 1) // We increment h.countAndHotIdx by 2 so that the counter in the upper
// 63 bits gets incremented by 1. At the same time, we get the new value
// back, which we can use to find the currently-hot counts.
n := atomic.AddUint64(&h.countAndHotIdx, 2)
hotCounts := &h.counts[n%2]
if i < len(h.upperBounds) {
atomic.AddUint64(&hotCounts.buckets[i], 1)
} }
atomic.AddUint64(&h.count, 1)
for { for {
oldBits := atomic.LoadUint64(&h.sumBits) oldBits := atomic.LoadUint64(&hotCounts.sumBits)
newBits := math.Float64bits(math.Float64frombits(oldBits) + v) newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
if atomic.CompareAndSwapUint64(&h.sumBits, oldBits, newBits) { if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
break break
} }
} }
// Increment count last as we take it as a signal that the observation
// is complete.
atomic.AddUint64(&hotCounts.count, 1)
} }
func (h *histogram) Write(out *dto.Metric) error { func (h *histogram) Write(out *dto.Metric) error {
his := &dto.Histogram{} var (
buckets := make([]*dto.Bucket, len(h.upperBounds)) his = &dto.Histogram{}
buckets = make([]*dto.Bucket, len(h.upperBounds))
hotCounts, coldCounts *histogramCounts
count uint64
)
his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&h.sumBits))) // For simplicity, we mutex the rest of this method. It is not in the
his.SampleCount = proto.Uint64(atomic.LoadUint64(&h.count)) // hot path, i.e. Observe is called much more often than Write. The
var count uint64 // complication of making Write lock-free isn't worth it.
h.writeMtx.Lock()
defer h.writeMtx.Unlock()
// This is a bit arcane, which is why the following spells out this if
// clause in English:
//
// If the currently-hot counts struct is #0, we atomically increment
// h.countAndHotIdx by 1 so that from now on Observe will use the counts
// struct #1. Furthermore, the atomic increment gives us the new value,
// which, in its most significant 63 bits, tells us the count of
// observations done so far up to and including currently ongoing
// observations still using the counts struct just changed from hot to
// cold. To have a normal uint64 for the count, we bitshift by 1 and
// save the result in count. We also set h.hotIdx to 1 for the next
// Write call, and we will refer to counts #1 as hotCounts and to counts
// #0 as coldCounts.
//
// If the currently-hot counts struct is #1, we do the corresponding
// things the other way round. We have to _decrement_ h.countAndHotIdx
// (which is a bit arcane in itself, as we have to express -1 with an
// unsigned int...).
if h.hotIdx == 0 {
count = atomic.AddUint64(&h.countAndHotIdx, 1) >> 1
h.hotIdx = 1
hotCounts = &h.counts[1]
coldCounts = &h.counts[0]
} else {
count = atomic.AddUint64(&h.countAndHotIdx, ^uint64(0)) >> 1 // Decrement.
h.hotIdx = 0
hotCounts = &h.counts[0]
coldCounts = &h.counts[1]
}
// Now we have to wait for the now-declared-cold counts to actually cool
// down, i.e. wait for all observations still using it to finish. That's
// the case once the count in the cold counts struct is the same as the
// one atomically retrieved from the upper 63bits of h.countAndHotIdx.
for {
if count == atomic.LoadUint64(&coldCounts.count) {
break
}
runtime.Gosched() // Let observations get work done.
}
his.SampleCount = proto.Uint64(count)
his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits)))
var cumCount uint64
for i, upperBound := range h.upperBounds { for i, upperBound := range h.upperBounds {
count += atomic.LoadUint64(&h.counts[i]) cumCount += atomic.LoadUint64(&coldCounts.buckets[i])
buckets[i] = &dto.Bucket{ buckets[i] = &dto.Bucket{
CumulativeCount: proto.Uint64(count), CumulativeCount: proto.Uint64(cumCount),
UpperBound: proto.Float64(upperBound), UpperBound: proto.Float64(upperBound),
} }
} }
his.Bucket = buckets his.Bucket = buckets
out.Histogram = his out.Histogram = his
out.Label = h.labelPairs out.Label = h.labelPairs
// Finally add all the cold counts to the new hot counts and reset the cold counts.
atomic.AddUint64(&hotCounts.count, count)
atomic.StoreUint64(&coldCounts.count, 0)
for {
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
newBits := math.Float64bits(math.Float64frombits(oldBits) + his.GetSampleSum())
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
atomic.StoreUint64(&coldCounts.sumBits, 0)
break
}
}
for i := range h.upperBounds {
atomic.AddUint64(&hotCounts.buckets[i], atomic.LoadUint64(&coldCounts.buckets[i]))
atomic.StoreUint64(&coldCounts.buckets[i], 0)
}
return nil return nil
} }
@ -454,7 +555,7 @@ func (h *constHistogram) Write(out *dto.Metric) error {
// bucket. // bucket.
// //
// NewConstHistogram returns an error if the length of labelValues is not // NewConstHistogram returns an error if the length of labelValues is not
// consistent with the variable labels in Desc. // consistent with the variable labels in Desc or if Desc is invalid.
func NewConstHistogram( func NewConstHistogram(
desc *Desc, desc *Desc,
count uint64, count uint64,
@ -462,6 +563,9 @@ func NewConstHistogram(
buckets map[float64]uint64, buckets map[float64]uint64,
labelValues ...string, labelValues ...string,
) (Metric, error) { ) (Metric, error) {
if desc.err != nil {
return nil, desc.err
}
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil { if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
return nil, err return nil, err
} }

View file

@ -61,15 +61,15 @@ func giveBuf(buf *bytes.Buffer) {
// name). // name).
// //
// Deprecated: Please note the issues described in the doc comment of // Deprecated: Please note the issues described in the doc comment of
// InstrumentHandler. You might want to consider using // InstrumentHandler. You might want to consider using promhttp.Handler instead.
// promhttp.InstrumentedHandler instead.
func Handler() http.Handler { func Handler() http.Handler {
return InstrumentHandler("prometheus", UninstrumentedHandler()) return InstrumentHandler("prometheus", UninstrumentedHandler())
} }
// UninstrumentedHandler returns an HTTP handler for the DefaultGatherer. // UninstrumentedHandler returns an HTTP handler for the DefaultGatherer.
// //
// Deprecated: Use promhttp.Handler instead. See there for further documentation. // Deprecated: Use promhttp.HandlerFor(DefaultGatherer, promhttp.HandlerOpts{})
// instead. See there for further documentation.
func UninstrumentedHandler() http.Handler { func UninstrumentedHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
mfs, err := DefaultGatherer.Gather() mfs, err := DefaultGatherer.Gather()
@ -149,21 +149,14 @@ var now nower = nowFunc(func() time.Time {
// (label name "method") and HTTP status code (label name "code"). // (label name "method") and HTTP status code (label name "code").
// //
// Deprecated: InstrumentHandler has several issues. Use the tooling provided in // Deprecated: InstrumentHandler has several issues. Use the tooling provided in
// package promhttp instead. The issues are the following: // package promhttp instead. The issues are the following: (1) It uses Summaries
// // rather than Histograms. Summaries are not useful if aggregation across
// - It uses Summaries rather than Histograms. Summaries are not useful if // multiple instances is required. (2) It uses microseconds as unit, which is
// aggregation across multiple instances is required. // deprecated and should be replaced by seconds. (3) The size of the request is
// // calculated in a separate goroutine. Since this calculator requires access to
// - It uses microseconds as unit, which is deprecated and should be replaced by // the request header, it creates a race with any writes to the header performed
// seconds. // during request handling. httputil.ReverseProxy is a prominent example for a
// // handler performing such writes. (4) It has additional issues with HTTP/2, cf.
// - The size of the request is calculated in a separate goroutine. Since this
// calculator requires access to the request header, it creates a race with
// any writes to the header performed during request handling.
// httputil.ReverseProxy is a prominent example for a handler
// performing such writes.
//
// - It has additional issues with HTTP/2, cf.
// https://github.com/prometheus/client_golang/issues/272. // https://github.com/prometheus/client_golang/issues/272.
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)
@ -307,7 +300,7 @@ func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.Respo
} }
func computeApproximateRequestSize(r *http.Request) <-chan int { func computeApproximateRequestSize(r *http.Request) <-chan int {
// Get URL length in current go routine for avoiding a race condition. // Get URL length in current goroutine for avoiding a race condition.
// HandlerFunc that runs in parallel may modify the URL. // HandlerFunc that runs in parallel may modify the URL.
s := 0 s := 0
if r.URL != nil { if r.URL != nil {

View file

@ -0,0 +1,85 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
import (
"sort"
dto "github.com/prometheus/client_model/go"
)
// metricSorter is a sortable slice of *dto.Metric.
type metricSorter []*dto.Metric
func (s metricSorter) Len() int {
return len(s)
}
func (s metricSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s metricSorter) Less(i, j int) bool {
if len(s[i].Label) != len(s[j].Label) {
// This should not happen. The metrics are
// inconsistent. However, we have to deal with the fact, as
// people might use custom collectors or metric family injection
// to create inconsistent metrics. So let's simply compare the
// number of labels in this case. That will still yield
// reproducible sorting.
return len(s[i].Label) < len(s[j].Label)
}
for n, lp := range s[i].Label {
vi := lp.GetValue()
vj := s[j].Label[n].GetValue()
if vi != vj {
return vi < vj
}
}
// We should never arrive here. Multiple metrics with the same
// label set in the same scrape will lead to undefined ingestion
// behavior. However, as above, we have to provide stable sorting
// here, even for inconsistent metrics. So sort equal metrics
// by their timestamp, with missing timestamps (implying "now")
// coming last.
if s[i].TimestampMs == nil {
return false
}
if s[j].TimestampMs == nil {
return true
}
return s[i].GetTimestampMs() < s[j].GetTimestampMs()
}
// NormalizeMetricFamilies returns a MetricFamily slice with empty
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
// the slice, with the contained Metrics sorted within each MetricFamily.
func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
for _, mf := range metricFamiliesByName {
sort.Sort(metricSorter(mf.Metric))
}
names := make([]string, 0, len(metricFamiliesByName))
for name, mf := range metricFamiliesByName {
if len(mf.Metric) > 0 {
names = append(names, name)
}
}
sort.Strings(names)
result := make([]*dto.MetricFamily, 0, len(names))
for _, name := range names {
result = append(result, metricFamiliesByName[name])
}
return result
}

View file

@ -1,3 +1,16 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus package prometheus
import ( import (

View file

@ -15,6 +15,9 @@ package prometheus
import ( import (
"strings" "strings"
"time"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
) )
@ -43,9 +46,8 @@ type Metric interface {
// While populating dto.Metric, it is the responsibility of the // While populating dto.Metric, it is the responsibility of the
// implementation to ensure validity of the Metric protobuf (like valid // implementation to ensure validity of the Metric protobuf (like valid
// UTF-8 strings or syntactically valid metric and label names). It is // UTF-8 strings or syntactically valid metric and label names). It is
// recommended to sort labels lexicographically. (Implementers may find // recommended to sort labels lexicographically. Callers of Write should
// LabelPairSorter useful for that.) Callers of Write should still make // still make sure of sorting if they depend on it.
// sure of sorting if they depend on it.
Write(*dto.Metric) error Write(*dto.Metric) error
// TODO(beorn7): The original rationale of passing in a pre-allocated // TODO(beorn7): The original rationale of passing in a pre-allocated
// dto.Metric protobuf to save allocations has disappeared. The // dto.Metric protobuf to save allocations has disappeared. The
@ -57,8 +59,9 @@ type Metric interface {
// implementation XXX has its own XXXOpts type, but in most cases, it is just be // implementation XXX has its own XXXOpts type, but in most cases, it is just be
// an alias of this type (which might change when the requirement arises.) // an alias of this type (which might change when the requirement arises.)
// //
// It is mandatory to set Name and Help to a non-empty string. All other fields // It is mandatory to set Name to a non-empty string. All other fields are
// are optional and can safely be left at their zero value. // optional and can safely be left at their zero value, although it is strongly
// encouraged to set a Help string.
type Opts struct { type Opts struct {
// Namespace, Subsystem, and Name are components of the fully-qualified // Namespace, Subsystem, and Name are components of the fully-qualified
// name of the Metric (created by joining these components with // name of the Metric (created by joining these components with
@ -69,7 +72,7 @@ type Opts struct {
Subsystem string Subsystem string
Name string Name string
// Help provides information about this metric. Mandatory! // Help provides information about this metric.
// //
// Metrics with the same fully-qualified name must have the same Help // Metrics with the same fully-qualified name must have the same Help
// string. // string.
@ -110,20 +113,19 @@ func BuildFQName(namespace, subsystem, name string) string {
return name return name
} }
// LabelPairSorter implements sort.Interface. It is used to sort a slice of // labelPairSorter implements sort.Interface. It is used to sort a slice of
// dto.LabelPair pointers. This is useful for implementing the Write method of // dto.LabelPair pointers.
// custom metrics. type labelPairSorter []*dto.LabelPair
type LabelPairSorter []*dto.LabelPair
func (s LabelPairSorter) Len() int { func (s labelPairSorter) Len() int {
return len(s) return len(s)
} }
func (s LabelPairSorter) Swap(i, j int) { func (s labelPairSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i] s[i], s[j] = s[j], s[i]
} }
func (s LabelPairSorter) Less(i, j int) bool { func (s labelPairSorter) Less(i, j int) bool {
return s[i].GetName() < s[j].GetName() return s[i].GetName() < s[j].GetName()
} }
@ -142,3 +144,31 @@ func NewInvalidMetric(desc *Desc, err error) Metric {
func (m *invalidMetric) Desc() *Desc { return m.desc } func (m *invalidMetric) Desc() *Desc { return m.desc }
func (m *invalidMetric) Write(*dto.Metric) error { return m.err } func (m *invalidMetric) Write(*dto.Metric) error { return m.err }
type timestampedMetric struct {
Metric
t time.Time
}
func (m timestampedMetric) Write(pb *dto.Metric) error {
e := m.Metric.Write(pb)
pb.TimestampMs = proto.Int64(m.t.Unix()*1000 + int64(m.t.Nanosecond()/1000000))
return e
}
// NewMetricWithTimestamp returns a new Metric wrapping the provided Metric in a
// way that it has an explicit timestamp set to the provided Time. This is only
// useful in rare cases as the timestamp of a Prometheus metric should usually
// be set by the Prometheus server during scraping. Exceptions include mirroring
// metrics with given timestamps from other metric
// sources.
//
// NewMetricWithTimestamp works best with MustNewConstMetric,
// MustNewConstHistogram, and MustNewConstSummary, see example.
//
// Currently, the exposition formats used by Prometheus are limited to
// millisecond resolution. Thus, the provided time will be rounded down to the
// next full millisecond value.
func NewMetricWithTimestamp(t time.Time, m Metric) Metric {
return timestampedMetric{Metric: m, t: t}
}

View file

@ -13,45 +13,74 @@
package prometheus package prometheus
import "github.com/prometheus/procfs" import (
"errors"
"os"
"github.com/prometheus/procfs"
)
type processCollector struct { type processCollector struct {
collectFn func(chan<- Metric) collectFn func(chan<- Metric)
pidFn func() (int, error) pidFn func() (int, error)
reportErrors bool
cpuTotal *Desc cpuTotal *Desc
openFDs, maxFDs *Desc openFDs, maxFDs *Desc
vsize, rss *Desc vsize, maxVsize *Desc
rss *Desc
startTime *Desc startTime *Desc
} }
// ProcessCollectorOpts defines the behavior of a process metrics collector
// created with NewProcessCollector.
type ProcessCollectorOpts struct {
// PidFn returns the PID of the process the collector collects metrics
// for. It is called upon each collection. By default, the PID of the
// current process is used, as determined on construction time by
// calling os.Getpid().
PidFn func() (int, error)
// If non-empty, each of the collected metrics is prefixed by the
// provided string and an underscore ("_").
Namespace string
// If true, any error encountered during collection is reported as an
// invalid metric (see NewInvalidMetric). Otherwise, errors are ignored
// and the collected metrics will be incomplete. (Possibly, no metrics
// will be collected at all.) While that's usually not desired, it is
// appropriate for the common "mix-in" of process metrics, where process
// metrics are nice to have, but failing to collect them should not
// disrupt the collection of the remaining metrics.
ReportErrors bool
}
// 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. The detailed behavior is defined by the provided
// ProcessCollectorOpts. The zero value of ProcessCollectorOpts creates a
// collector for the current process with an empty namespace string and no error
// reporting.
// //
// Currently, the collector depends on a Linux-style proc filesystem and // Currently, the collector depends on a Linux-style proc filesystem and
// therefore only exports metrics for Linux. // therefore only exports metrics for Linux.
func NewProcessCollector(pid int, namespace string) Collector { //
return NewProcessCollectorPIDFn( // Note: An older version of this function had the following signature:
func() (int, error) { return pid, nil }, //
namespace, // NewProcessCollector(pid int, namespace string) Collector
) //
} // Most commonly, it was called as
//
// NewProcessCollectorPIDFn works like NewProcessCollector but the process ID is // NewProcessCollector(os.Getpid(), "")
// determined on each collect anew by calling the given pidFn function. //
func NewProcessCollectorPIDFn( // The following call of the current version is equivalent to the above:
pidFn func() (int, error), //
namespace string, // NewProcessCollector(ProcessCollectorOpts{})
) Collector { func NewProcessCollector(opts ProcessCollectorOpts) Collector {
ns := "" ns := ""
if len(namespace) > 0 { if len(opts.Namespace) > 0 {
ns = namespace + "_" ns = opts.Namespace + "_"
} }
c := processCollector{ c := &processCollector{
pidFn: pidFn, reportErrors: opts.ReportErrors,
collectFn: func(chan<- Metric) {},
cpuTotal: NewDesc( cpuTotal: NewDesc(
ns+"process_cpu_seconds_total", ns+"process_cpu_seconds_total",
"Total user and system CPU time spent in seconds.", "Total user and system CPU time spent in seconds.",
@ -72,6 +101,11 @@ func NewProcessCollectorPIDFn(
"Virtual memory size in bytes.", "Virtual memory size in bytes.",
nil, nil, nil, nil,
), ),
maxVsize: NewDesc(
ns+"process_virtual_memory_max_bytes",
"Maximum amount of virtual memory available in bytes.",
nil, nil,
),
rss: NewDesc( rss: NewDesc(
ns+"process_resident_memory_bytes", ns+"process_resident_memory_bytes",
"Resident memory size in bytes.", "Resident memory size in bytes.",
@ -84,12 +118,23 @@ func NewProcessCollectorPIDFn(
), ),
} }
if opts.PidFn == nil {
pid := os.Getpid()
c.pidFn = func() (int, error) { return pid, nil }
} else {
c.pidFn = opts.PidFn
}
// Set up process metric collection if supported by the runtime. // Set up process metric collection if supported by the runtime.
if _, err := procfs.NewStat(); err == nil { if _, err := procfs.NewStat(); err == nil {
c.collectFn = c.processCollect c.collectFn = c.processCollect
} else {
c.collectFn = func(ch chan<- Metric) {
c.reportError(ch, nil, errors.New("process metrics not supported on this platform"))
}
} }
return &c return c
} }
// Describe returns all descriptions of the collector. // Describe returns all descriptions of the collector.
@ -98,6 +143,7 @@ func (c *processCollector) Describe(ch chan<- *Desc) {
ch <- c.openFDs ch <- c.openFDs
ch <- c.maxFDs ch <- c.maxFDs
ch <- c.vsize ch <- c.vsize
ch <- c.maxVsize
ch <- c.rss ch <- c.rss
ch <- c.startTime ch <- c.startTime
} }
@ -107,16 +153,16 @@ func (c *processCollector) Collect(ch chan<- Metric) {
c.collectFn(ch) c.collectFn(ch)
} }
// TODO(ts): Bring back error reporting by reverting 7faf9e7 as soon as the
// client allows users to configure the error behavior.
func (c *processCollector) processCollect(ch chan<- Metric) { func (c *processCollector) processCollect(ch chan<- Metric) {
pid, err := c.pidFn() pid, err := c.pidFn()
if err != nil { if err != nil {
c.reportError(ch, nil, err)
return return
} }
p, err := procfs.NewProc(pid) p, err := procfs.NewProc(pid)
if err != nil { if err != nil {
c.reportError(ch, nil, err)
return return
} }
@ -126,14 +172,33 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory())) ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory()))
if startTime, err := stat.StartTime(); err == nil { if startTime, err := stat.StartTime(); err == nil {
ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime) ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime)
} else {
c.reportError(ch, c.startTime, err)
} }
} else {
c.reportError(ch, nil, err)
} }
if fds, err := p.FileDescriptorsLen(); err == nil { if fds, err := p.FileDescriptorsLen(); err == nil {
ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds)) ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds))
} else {
c.reportError(ch, c.openFDs, err)
} }
if limits, err := p.NewLimits(); err == nil { if limits, err := p.NewLimits(); err == nil {
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles)) ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles))
ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(limits.AddressSpace))
} else {
c.reportError(ch, nil, err)
} }
} }
func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error) {
if !c.reportErrors {
return
}
if desc == nil {
desc = NewInvalidDesc(err)
}
ch <- NewInvalidMetric(desc, err)
}

View file

@ -76,16 +76,16 @@ type flusherDelegator struct{ *responseWriterDelegator }
type hijackerDelegator struct{ *responseWriterDelegator } type hijackerDelegator struct{ *responseWriterDelegator }
type readerFromDelegator struct{ *responseWriterDelegator } type readerFromDelegator struct{ *responseWriterDelegator }
func (d *closeNotifierDelegator) CloseNotify() <-chan bool { func (d closeNotifierDelegator) CloseNotify() <-chan bool {
return d.ResponseWriter.(http.CloseNotifier).CloseNotify() return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
} }
func (d *flusherDelegator) Flush() { func (d flusherDelegator) Flush() {
d.ResponseWriter.(http.Flusher).Flush() d.ResponseWriter.(http.Flusher).Flush()
} }
func (d *hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) { func (d hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return d.ResponseWriter.(http.Hijacker).Hijack() return d.ResponseWriter.(http.Hijacker).Hijack()
} }
func (d *readerFromDelegator) ReadFrom(re io.Reader) (int64, error) { func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
if !d.wroteHeader { if !d.wroteHeader {
d.WriteHeader(http.StatusOK) d.WriteHeader(http.StatusOK)
} }
@ -102,34 +102,34 @@ func init() {
return d return d
} }
pickDelegator[closeNotifier] = func(d *responseWriterDelegator) delegator { // 1 pickDelegator[closeNotifier] = func(d *responseWriterDelegator) delegator { // 1
return &closeNotifierDelegator{d} return closeNotifierDelegator{d}
} }
pickDelegator[flusher] = func(d *responseWriterDelegator) delegator { // 2 pickDelegator[flusher] = func(d *responseWriterDelegator) delegator { // 2
return &flusherDelegator{d} return flusherDelegator{d}
} }
pickDelegator[flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 3 pickDelegator[flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 3
return struct { return struct {
*responseWriterDelegator *responseWriterDelegator
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
}{d, &flusherDelegator{d}, &closeNotifierDelegator{d}} }{d, flusherDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[hijacker] = func(d *responseWriterDelegator) delegator { // 4 pickDelegator[hijacker] = func(d *responseWriterDelegator) delegator { // 4
return &hijackerDelegator{d} return hijackerDelegator{d}
} }
pickDelegator[hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 5 pickDelegator[hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 5
return struct { return struct {
*responseWriterDelegator *responseWriterDelegator
http.Hijacker http.Hijacker
http.CloseNotifier http.CloseNotifier
}{d, &hijackerDelegator{d}, &closeNotifierDelegator{d}} }{d, hijackerDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 6 pickDelegator[hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 6
return struct { return struct {
*responseWriterDelegator *responseWriterDelegator
http.Hijacker http.Hijacker
http.Flusher http.Flusher
}{d, &hijackerDelegator{d}, &flusherDelegator{d}} }{d, hijackerDelegator{d}, flusherDelegator{d}}
} }
pickDelegator[hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 7 pickDelegator[hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 7
return struct { return struct {
@ -137,7 +137,7 @@ func init() {
http.Hijacker http.Hijacker
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
}{d, &hijackerDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}} }{d, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[readerFrom] = func(d *responseWriterDelegator) delegator { // 8 pickDelegator[readerFrom] = func(d *responseWriterDelegator) delegator { // 8
return readerFromDelegator{d} return readerFromDelegator{d}
@ -147,14 +147,14 @@ func init() {
*responseWriterDelegator *responseWriterDelegator
io.ReaderFrom io.ReaderFrom
http.CloseNotifier http.CloseNotifier
}{d, &readerFromDelegator{d}, &closeNotifierDelegator{d}} }{d, readerFromDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 10 pickDelegator[readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 10
return struct { return struct {
*responseWriterDelegator *responseWriterDelegator
io.ReaderFrom io.ReaderFrom
http.Flusher http.Flusher
}{d, &readerFromDelegator{d}, &flusherDelegator{d}} }{d, readerFromDelegator{d}, flusherDelegator{d}}
} }
pickDelegator[readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 11 pickDelegator[readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 11
return struct { return struct {
@ -162,14 +162,14 @@ func init() {
io.ReaderFrom io.ReaderFrom
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
}{d, &readerFromDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}} }{d, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 12 pickDelegator[readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 12
return struct { return struct {
*responseWriterDelegator *responseWriterDelegator
io.ReaderFrom io.ReaderFrom
http.Hijacker http.Hijacker
}{d, &readerFromDelegator{d}, &hijackerDelegator{d}} }{d, readerFromDelegator{d}, hijackerDelegator{d}}
} }
pickDelegator[readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 13 pickDelegator[readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 13
return struct { return struct {
@ -177,7 +177,7 @@ func init() {
io.ReaderFrom io.ReaderFrom
http.Hijacker http.Hijacker
http.CloseNotifier http.CloseNotifier
}{d, &readerFromDelegator{d}, &hijackerDelegator{d}, &closeNotifierDelegator{d}} }{d, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 14 pickDelegator[readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 14
return struct { return struct {
@ -185,7 +185,7 @@ func init() {
io.ReaderFrom io.ReaderFrom
http.Hijacker http.Hijacker
http.Flusher http.Flusher
}{d, &readerFromDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}} }{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
} }
pickDelegator[readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 15 pickDelegator[readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 15
return struct { return struct {
@ -194,6 +194,6 @@ func init() {
http.Hijacker http.Hijacker
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
}{d, &readerFromDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}} }{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
} }
} }

View file

@ -22,27 +22,27 @@ import (
type pusherDelegator struct{ *responseWriterDelegator } type pusherDelegator struct{ *responseWriterDelegator }
func (d *pusherDelegator) Push(target string, opts *http.PushOptions) error { func (d pusherDelegator) Push(target string, opts *http.PushOptions) error {
return d.ResponseWriter.(http.Pusher).Push(target, opts) return d.ResponseWriter.(http.Pusher).Push(target, opts)
} }
func init() { func init() {
pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16 pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16
return &pusherDelegator{d} return pusherDelegator{d}
} }
pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17 pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17
return struct { return struct {
*responseWriterDelegator *responseWriterDelegator
http.Pusher http.Pusher
http.CloseNotifier http.CloseNotifier
}{d, &pusherDelegator{d}, &closeNotifierDelegator{d}} }{d, pusherDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[pusher+flusher] = func(d *responseWriterDelegator) delegator { // 18 pickDelegator[pusher+flusher] = func(d *responseWriterDelegator) delegator { // 18
return struct { return struct {
*responseWriterDelegator *responseWriterDelegator
http.Pusher http.Pusher
http.Flusher http.Flusher
}{d, &pusherDelegator{d}, &flusherDelegator{d}} }{d, pusherDelegator{d}, flusherDelegator{d}}
} }
pickDelegator[pusher+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 19 pickDelegator[pusher+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 19
return struct { return struct {
@ -50,14 +50,14 @@ func init() {
http.Pusher http.Pusher
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
}{d, &pusherDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}} }{d, pusherDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[pusher+hijacker] = func(d *responseWriterDelegator) delegator { // 20 pickDelegator[pusher+hijacker] = func(d *responseWriterDelegator) delegator { // 20
return struct { return struct {
*responseWriterDelegator *responseWriterDelegator
http.Pusher http.Pusher
http.Hijacker http.Hijacker
}{d, &pusherDelegator{d}, &hijackerDelegator{d}} }{d, pusherDelegator{d}, hijackerDelegator{d}}
} }
pickDelegator[pusher+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 21 pickDelegator[pusher+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 21
return struct { return struct {
@ -65,7 +65,7 @@ func init() {
http.Pusher http.Pusher
http.Hijacker http.Hijacker
http.CloseNotifier http.CloseNotifier
}{d, &pusherDelegator{d}, &hijackerDelegator{d}, &closeNotifierDelegator{d}} }{d, pusherDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[pusher+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 22 pickDelegator[pusher+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 22
return struct { return struct {
@ -73,7 +73,7 @@ func init() {
http.Pusher http.Pusher
http.Hijacker http.Hijacker
http.Flusher http.Flusher
}{d, &pusherDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}} }{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
} }
pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23 pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23
return struct { return struct {
@ -82,14 +82,14 @@ func init() {
http.Hijacker http.Hijacker
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
}{d, &pusherDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}} }{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[pusher+readerFrom] = func(d *responseWriterDelegator) delegator { // 24 pickDelegator[pusher+readerFrom] = func(d *responseWriterDelegator) delegator { // 24
return struct { return struct {
*responseWriterDelegator *responseWriterDelegator
http.Pusher http.Pusher
io.ReaderFrom io.ReaderFrom
}{d, &pusherDelegator{d}, &readerFromDelegator{d}} }{d, pusherDelegator{d}, readerFromDelegator{d}}
} }
pickDelegator[pusher+readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 25 pickDelegator[pusher+readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 25
return struct { return struct {
@ -97,7 +97,7 @@ func init() {
http.Pusher http.Pusher
io.ReaderFrom io.ReaderFrom
http.CloseNotifier http.CloseNotifier
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &closeNotifierDelegator{d}} }{d, pusherDelegator{d}, readerFromDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[pusher+readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 26 pickDelegator[pusher+readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 26
return struct { return struct {
@ -105,7 +105,7 @@ func init() {
http.Pusher http.Pusher
io.ReaderFrom io.ReaderFrom
http.Flusher http.Flusher
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &flusherDelegator{d}} }{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}}
} }
pickDelegator[pusher+readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 27 pickDelegator[pusher+readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 27
return struct { return struct {
@ -114,7 +114,7 @@ func init() {
io.ReaderFrom io.ReaderFrom
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}} }{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[pusher+readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 28 pickDelegator[pusher+readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 28
return struct { return struct {
@ -122,7 +122,7 @@ func init() {
http.Pusher http.Pusher
io.ReaderFrom io.ReaderFrom
http.Hijacker http.Hijacker
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}} }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}}
} }
pickDelegator[pusher+readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 29 pickDelegator[pusher+readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 29
return struct { return struct {
@ -131,7 +131,7 @@ func init() {
io.ReaderFrom io.ReaderFrom
http.Hijacker http.Hijacker
http.CloseNotifier http.CloseNotifier
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}, &closeNotifierDelegator{d}} }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
} }
pickDelegator[pusher+readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 30 pickDelegator[pusher+readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 30
return struct { return struct {
@ -140,7 +140,7 @@ func init() {
io.ReaderFrom io.ReaderFrom
http.Hijacker http.Hijacker
http.Flusher http.Flusher
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}} }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
} }
pickDelegator[pusher+readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 31 pickDelegator[pusher+readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 31
return struct { return struct {
@ -150,7 +150,7 @@ func init() {
http.Hijacker http.Hijacker
http.Flusher http.Flusher
http.CloseNotifier http.CloseNotifier
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}} }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
} }
} }

View file

@ -15,17 +15,18 @@ package prometheus
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"os"
"runtime" "runtime"
"sort" "sort"
"strings"
"sync" "sync"
"unicode/utf8" "unicode/utf8"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/prometheus/client_golang/prometheus/internal"
) )
const ( const (
@ -52,7 +53,7 @@ var (
) )
func init() { func init() {
MustRegister(NewProcessCollector(os.Getpid(), "")) MustRegister(NewProcessCollector(ProcessCollectorOpts{}))
MustRegister(NewGoCollector()) MustRegister(NewGoCollector())
} }
@ -68,7 +69,8 @@ func NewRegistry() *Registry {
// NewPedanticRegistry returns a registry that checks during collection if each // NewPedanticRegistry returns a registry that checks during collection if each
// collected Metric is consistent with its reported Desc, and if the Desc has // collected Metric is consistent with its reported Desc, and if the Desc has
// actually been registered with the registry. // actually been registered with the registry. Unchecked Collectors (those whose
// Describe methed does not yield any descriptors) are excluded from the check.
// //
// Usually, a Registry will be happy as long as the union of all collected // Usually, a Registry will be happy as long as the union of all collected
// Metrics is consistent and valid even if some metrics are not consistent with // Metrics is consistent and valid even if some metrics are not consistent with
@ -98,6 +100,14 @@ type Registerer interface {
// returned error is an instance of AlreadyRegisteredError, which // returned error is an instance of AlreadyRegisteredError, which
// contains the previously registered Collector. // contains the previously registered Collector.
// //
// A Collector whose Describe method does not yield any Desc is treated
// as unchecked. Registration will always succeed. No check for
// re-registering (see previous paragraph) is performed. Thus, the
// caller is responsible for not double-registering the same unchecked
// Collector, and for providing a Collector that will not cause
// inconsistent metrics on collection. (This would lead to scrape
// errors.)
//
// It is in general not safe to register the same Collector multiple // It is in general not safe to register the same Collector multiple
// times concurrently. // times concurrently.
Register(Collector) error Register(Collector) error
@ -108,7 +118,9 @@ type Registerer interface {
// Unregister unregisters the Collector that equals the Collector passed // Unregister unregisters the Collector that equals the Collector passed
// in as an argument. (Two Collectors are considered equal if their // in as an argument. (Two Collectors are considered equal if their
// Describe method yields the same set of descriptors.) The function // Describe method yields the same set of descriptors.) The function
// returns whether a Collector was unregistered. // returns whether a Collector was unregistered. Note that an unchecked
// Collector cannot be unregistered (as its Describe method does not
// yield any descriptor).
// //
// Note that even after unregistering, it will not be possible to // Note that even after unregistering, it will not be possible to
// register a new Collector that is inconsistent with the unregistered // register a new Collector that is inconsistent with the unregistered
@ -243,6 +255,7 @@ type Registry struct {
collectorsByID map[uint64]Collector // ID is a hash of the descIDs. collectorsByID map[uint64]Collector // ID is a hash of the descIDs.
descIDs map[uint64]struct{} descIDs map[uint64]struct{}
dimHashesByName map[string]uint64 dimHashesByName map[string]uint64
uncheckedCollectors []Collector
pedanticChecksEnabled bool pedanticChecksEnabled bool
} }
@ -300,9 +313,10 @@ func (r *Registry) Register(c Collector) error {
} }
} }
} }
// Did anything happen at all? // A Collector yielding no Desc at all is considered unchecked.
if len(newDescIDs) == 0 { if len(newDescIDs) == 0 {
return errors.New("collector has no descriptors") r.uncheckedCollectors = append(r.uncheckedCollectors, c)
return nil
} }
if existing, exists := r.collectorsByID[collectorID]; exists { if existing, exists := r.collectorsByID[collectorID]; exists {
return AlreadyRegisteredError{ return AlreadyRegisteredError{
@ -376,19 +390,24 @@ func (r *Registry) MustRegister(cs ...Collector) {
// Gather implements Gatherer. // Gather implements Gatherer.
func (r *Registry) Gather() ([]*dto.MetricFamily, error) { func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
var ( var (
metricChan = make(chan Metric, capMetricChan) checkedMetricChan = make(chan Metric, capMetricChan)
metricHashes = map[uint64]struct{}{} uncheckedMetricChan = make(chan Metric, capMetricChan)
wg sync.WaitGroup metricHashes = map[uint64]struct{}{}
errs MultiError // The collected errors to return in the end. wg sync.WaitGroup
registeredDescIDs map[uint64]struct{} // Only used for pedantic checks errs MultiError // The collected errors to return in the end.
registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
) )
r.mtx.RLock() r.mtx.RLock()
goroutineBudget := len(r.collectorsByID) goroutineBudget := len(r.collectorsByID) + len(r.uncheckedCollectors)
metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName)) metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
collectors := make(chan Collector, len(r.collectorsByID)) checkedCollectors := make(chan Collector, len(r.collectorsByID))
uncheckedCollectors := make(chan Collector, len(r.uncheckedCollectors))
for _, collector := range r.collectorsByID { for _, collector := range r.collectorsByID {
collectors <- collector checkedCollectors <- collector
}
for _, collector := range r.uncheckedCollectors {
uncheckedCollectors <- collector
} }
// In case pedantic checks are enabled, we have to copy the map before // In case pedantic checks are enabled, we have to copy the map before
// giving up the RLock. // giving up the RLock.
@ -405,12 +424,14 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
collectWorker := func() { collectWorker := func() {
for { for {
select { select {
case collector := <-collectors: case collector := <-checkedCollectors:
collector.Collect(metricChan) collector.Collect(checkedMetricChan)
wg.Done() case collector := <-uncheckedCollectors:
collector.Collect(uncheckedMetricChan)
default: default:
return return
} }
wg.Done()
} }
} }
@ -418,53 +439,96 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
go collectWorker() go collectWorker()
goroutineBudget-- goroutineBudget--
// Close the metricChan once all collectors are collected. // Close checkedMetricChan and uncheckedMetricChan once all collectors
// are collected.
go func() { go func() {
wg.Wait() wg.Wait()
close(metricChan) close(checkedMetricChan)
close(uncheckedMetricChan)
}() }()
// Drain metricChan in case of premature return. // Drain checkedMetricChan and uncheckedMetricChan in case of premature return.
defer func() { defer func() {
for range metricChan { if checkedMetricChan != nil {
for range checkedMetricChan {
}
}
if uncheckedMetricChan != nil {
for range uncheckedMetricChan {
}
} }
}() }()
collectLoop: // Copy the channel references so we can nil them out later to remove
// them from the select statements below.
cmc := checkedMetricChan
umc := uncheckedMetricChan
for { for {
select { select {
case metric, ok := <-metricChan: case metric, ok := <-cmc:
if !ok { if !ok {
// metricChan is closed, we are done. cmc = nil
break collectLoop break
} }
errs.Append(processMetric( errs.Append(processMetric(
metric, metricFamiliesByName, metric, metricFamiliesByName,
metricHashes, metricHashes,
registeredDescIDs, registeredDescIDs,
)) ))
case metric, ok := <-umc:
if !ok {
umc = nil
break
}
errs.Append(processMetric(
metric, metricFamiliesByName,
metricHashes,
nil,
))
default: default:
if goroutineBudget <= 0 || len(collectors) == 0 { if goroutineBudget <= 0 || len(checkedCollectors)+len(uncheckedCollectors) == 0 {
// All collectors are already being worked on or // All collectors are already being worked on or
// we have already as many goroutines started as // we have already as many goroutines started as
// there are collectors. Just process metrics // there are collectors. Do the same as above,
// from now on. // just without the default.
for metric := range metricChan { select {
case metric, ok := <-cmc:
if !ok {
cmc = nil
break
}
errs.Append(processMetric( errs.Append(processMetric(
metric, metricFamiliesByName, metric, metricFamiliesByName,
metricHashes, metricHashes,
registeredDescIDs, registeredDescIDs,
)) ))
case metric, ok := <-umc:
if !ok {
umc = nil
break
}
errs.Append(processMetric(
metric, metricFamiliesByName,
metricHashes,
nil,
))
} }
break collectLoop break
} }
// Start more workers. // Start more workers.
go collectWorker() go collectWorker()
goroutineBudget-- goroutineBudget--
runtime.Gosched() runtime.Gosched()
} }
// Once both checkedMetricChan and uncheckdMetricChan are closed
// and drained, the contraption above will nil out cmc and umc,
// and then we can leave the collect loop here.
if cmc == nil && umc == nil {
break
}
} }
return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap() return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
} }
// processMetric is an internal helper method only used by the Gather method. // processMetric is an internal helper method only used by the Gather method.
@ -475,12 +539,17 @@ func processMetric(
registeredDescIDs map[uint64]struct{}, registeredDescIDs map[uint64]struct{},
) error { ) error {
desc := metric.Desc() desc := metric.Desc()
// Wrapped metrics collected by an unchecked Collector can have an
// invalid Desc.
if desc.err != nil {
return desc.err
}
dtoMetric := &dto.Metric{} dtoMetric := &dto.Metric{}
if err := metric.Write(dtoMetric); err != nil { if err := metric.Write(dtoMetric); err != nil {
return fmt.Errorf("error collecting metric %v: %s", desc, err) return fmt.Errorf("error collecting metric %v: %s", desc, err)
} }
metricFamily, ok := metricFamiliesByName[desc.fqName] metricFamily, ok := metricFamiliesByName[desc.fqName]
if ok { if ok { // Existing name.
if metricFamily.GetHelp() != desc.help { if metricFamily.GetHelp() != desc.help {
return fmt.Errorf( return fmt.Errorf(
"collected metric %s %s has help %q but should have %q", "collected metric %s %s has help %q but should have %q",
@ -527,7 +596,7 @@ func processMetric(
default: default:
panic("encountered MetricFamily with invalid type") panic("encountered MetricFamily with invalid type")
} }
} else { } else { // New name.
metricFamily = &dto.MetricFamily{} metricFamily = &dto.MetricFamily{}
metricFamily.Name = proto.String(desc.fqName) metricFamily.Name = proto.String(desc.fqName)
metricFamily.Help = proto.String(desc.help) metricFamily.Help = proto.String(desc.help)
@ -546,6 +615,9 @@ func processMetric(
default: default:
return fmt.Errorf("empty metric collected: %s", dtoMetric) return fmt.Errorf("empty metric collected: %s", dtoMetric)
} }
if err := checkSuffixCollisions(metricFamily, metricFamiliesByName); err != nil {
return err
}
metricFamiliesByName[desc.fqName] = metricFamily metricFamiliesByName[desc.fqName] = metricFamily
} }
if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes); err != nil { if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes); err != nil {
@ -626,6 +698,10 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
existingMF.Name = mf.Name existingMF.Name = mf.Name
existingMF.Help = mf.Help existingMF.Help = mf.Help
existingMF.Type = mf.Type existingMF.Type = mf.Type
if err := checkSuffixCollisions(existingMF, metricFamiliesByName); err != nil {
errs = append(errs, err)
continue
}
metricFamiliesByName[mf.GetName()] = existingMF metricFamiliesByName[mf.GetName()] = existingMF
} }
for _, m := range mf.Metric { for _, m := range mf.Metric {
@ -637,72 +713,67 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
} }
} }
} }
return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap() return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
} }
// metricSorter is a sortable slice of *dto.Metric. // checkSuffixCollisions checks for collisions with the “magic” suffixes the
type metricSorter []*dto.Metric // Prometheus text format and the internal metric representation of the
// Prometheus server add while flattening Summaries and Histograms.
func (s metricSorter) Len() int { func checkSuffixCollisions(mf *dto.MetricFamily, mfs map[string]*dto.MetricFamily) error {
return len(s) var (
} newName = mf.GetName()
newType = mf.GetType()
func (s metricSorter) Swap(i, j int) { newNameWithoutSuffix = ""
s[i], s[j] = s[j], s[i] )
} switch {
case strings.HasSuffix(newName, "_count"):
func (s metricSorter) Less(i, j int) bool { newNameWithoutSuffix = newName[:len(newName)-6]
if len(s[i].Label) != len(s[j].Label) { case strings.HasSuffix(newName, "_sum"):
// This should not happen. The metrics are newNameWithoutSuffix = newName[:len(newName)-4]
// inconsistent. However, we have to deal with the fact, as case strings.HasSuffix(newName, "_bucket"):
// people might use custom collectors or metric family injection newNameWithoutSuffix = newName[:len(newName)-7]
// to create inconsistent metrics. So let's simply compare the
// number of labels in this case. That will still yield
// reproducible sorting.
return len(s[i].Label) < len(s[j].Label)
} }
for n, lp := range s[i].Label { if newNameWithoutSuffix != "" {
vi := lp.GetValue() if existingMF, ok := mfs[newNameWithoutSuffix]; ok {
vj := s[j].Label[n].GetValue() switch existingMF.GetType() {
if vi != vj { case dto.MetricType_SUMMARY:
return vi < vj if !strings.HasSuffix(newName, "_bucket") {
return fmt.Errorf(
"collected metric named %q collides with previously collected summary named %q",
newName, newNameWithoutSuffix,
)
}
case dto.MetricType_HISTOGRAM:
return fmt.Errorf(
"collected metric named %q collides with previously collected histogram named %q",
newName, newNameWithoutSuffix,
)
}
} }
} }
if newType == dto.MetricType_SUMMARY || newType == dto.MetricType_HISTOGRAM {
// We should never arrive here. Multiple metrics with the same if _, ok := mfs[newName+"_count"]; ok {
// label set in the same scrape will lead to undefined ingestion return fmt.Errorf(
// behavior. However, as above, we have to provide stable sorting "collected histogram or summary named %q collides with previously collected metric named %q",
// here, even for inconsistent metrics. So sort equal metrics newName, newName+"_count",
// by their timestamp, with missing timestamps (implying "now") )
// coming last. }
if s[i].TimestampMs == nil { if _, ok := mfs[newName+"_sum"]; ok {
return false return fmt.Errorf(
} "collected histogram or summary named %q collides with previously collected metric named %q",
if s[j].TimestampMs == nil { newName, newName+"_sum",
return true )
}
return s[i].GetTimestampMs() < s[j].GetTimestampMs()
}
// normalizeMetricFamilies returns a MetricFamily slice with empty
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
// the slice, with the contained Metrics sorted within each MetricFamily.
func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
for _, mf := range metricFamiliesByName {
sort.Sort(metricSorter(mf.Metric))
}
names := make([]string, 0, len(metricFamiliesByName))
for name, mf := range metricFamiliesByName {
if len(mf.Metric) > 0 {
names = append(names, name)
} }
} }
sort.Strings(names) if newType == dto.MetricType_HISTOGRAM {
result := make([]*dto.MetricFamily, 0, len(names)) if _, ok := mfs[newName+"_bucket"]; ok {
for _, name := range names { return fmt.Errorf(
result = append(result, metricFamiliesByName[name]) "collected histogram named %q collides with previously collected metric named %q",
newName, newName+"_bucket",
)
}
} }
return result return nil
} }
// checkMetricConsistency checks if the provided Metric is consistent with the // checkMetricConsistency checks if the provided Metric is consistent with the
@ -721,14 +792,28 @@ func checkMetricConsistency(
metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil || metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil ||
metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil { metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil {
return fmt.Errorf( return fmt.Errorf(
"collected metric %s %s is not a %s", "collected metric %q { %s} is not a %s",
metricFamily.GetName(), dtoMetric, metricFamily.GetType(), metricFamily.GetName(), dtoMetric, metricFamily.GetType(),
) )
} }
for _, labelPair := range dtoMetric.GetLabel() { for _, labelPair := range dtoMetric.GetLabel() {
if !checkLabelName(labelPair.GetName()) {
return fmt.Errorf(
"collected metric %q { %s} has a label with an invalid name: %s",
metricFamily.GetName(), dtoMetric, labelPair.GetName(),
)
}
if dtoMetric.Summary != nil && labelPair.GetName() == quantileLabel {
return fmt.Errorf(
"collected metric %q { %s} must not have an explicit %q label",
metricFamily.GetName(), dtoMetric, quantileLabel,
)
}
if !utf8.ValidString(labelPair.GetValue()) { if !utf8.ValidString(labelPair.GetValue()) {
return fmt.Errorf("collected metric's label %s is not utf8: %#v", labelPair.GetName(), labelPair.GetValue()) return fmt.Errorf(
"collected metric %q { %s} has a label named %q whose value is not utf8: %#v",
metricFamily.GetName(), dtoMetric, labelPair.GetName(), labelPair.GetValue())
} }
} }
@ -738,7 +823,7 @@ func checkMetricConsistency(
h = hashAddByte(h, separatorByte) h = hashAddByte(h, separatorByte)
// Make sure label pairs are sorted. We depend on it for the consistency // Make sure label pairs are sorted. We depend on it for the consistency
// check. // check.
sort.Sort(LabelPairSorter(dtoMetric.Label)) sort.Sort(labelPairSorter(dtoMetric.Label))
for _, lp := range dtoMetric.Label { for _, lp := range dtoMetric.Label {
h = hashAdd(h, lp.GetName()) h = hashAdd(h, lp.GetName())
h = hashAddByte(h, separatorByte) h = hashAddByte(h, separatorByte)
@ -747,7 +832,7 @@ func checkMetricConsistency(
} }
if _, exists := metricHashes[h]; exists { if _, exists := metricHashes[h]; exists {
return fmt.Errorf( return fmt.Errorf(
"collected metric %s %s was collected before with the same name and label values", "collected metric %q { %s} was collected before with the same name and label values",
metricFamily.GetName(), dtoMetric, metricFamily.GetName(), dtoMetric,
) )
} }
@ -782,7 +867,7 @@ func checkDescConsistency(
metricFamily.GetName(), dtoMetric, desc, metricFamily.GetName(), dtoMetric, desc,
) )
} }
sort.Sort(LabelPairSorter(lpsFromDesc)) sort.Sort(labelPairSorter(lpsFromDesc))
for i, lpFromDesc := range lpsFromDesc { for i, lpFromDesc := range lpsFromDesc {
lpFromMetric := dtoMetric.Label[i] lpFromMetric := dtoMetric.Label[i]
if lpFromDesc.GetName() != lpFromMetric.GetName() || if lpFromDesc.GetName() != lpFromMetric.GetName() ||

View file

@ -37,7 +37,7 @@ const quantileLabel = "quantile"
// A typical use-case is the observation of request latencies. By default, a // A typical use-case is the observation of request latencies. By default, a
// Summary provides the median, the 90th and the 99th percentile of the latency // Summary provides the median, the 90th and the 99th percentile of the latency
// as rank estimations. However, the default behavior will change in the // as rank estimations. However, the default behavior will change in the
// upcoming v0.10 of the library. There will be no rank estiamtions at all by // upcoming v0.10 of the library. There will be no rank estimations at all by
// default. For a sane transition, it is recommended to set the desired rank // default. For a sane transition, it is recommended to set the desired rank
// estimations explicitly. // estimations explicitly.
// //
@ -81,10 +81,10 @@ const (
) )
// SummaryOpts bundles the options for creating a Summary metric. It is // SummaryOpts bundles the options for creating a Summary metric. It is
// mandatory to set Name and Help to a non-empty string. While all other fields // mandatory to set Name to a non-empty string. While all other fields are
// are optional and can safely be left at their zero value, it is recommended to // optional and can safely be left at their zero value, it is recommended to set
// explicitly set the Objectives field to the desired value as the default value // a help string and to explicitly set the Objectives field to the desired value
// will change in the upcoming v0.10 of the library. // as the default value will change in the upcoming v0.10 of the library.
type SummaryOpts struct { type SummaryOpts struct {
// Namespace, Subsystem, and Name are components of the fully-qualified // Namespace, Subsystem, and Name are components of the fully-qualified
// name of the Summary (created by joining these components with // name of the Summary (created by joining these components with
@ -95,7 +95,7 @@ type SummaryOpts struct {
Subsystem string Subsystem string
Name string Name string
// Help provides information about this Summary. Mandatory! // Help provides information about this Summary.
// //
// Metrics with the same fully-qualified name must have the same Help // Metrics with the same fully-qualified name must have the same Help
// string. // string.
@ -105,6 +105,11 @@ type SummaryOpts struct {
// with the same fully-qualified name must have the same label names in // with the same fully-qualified name must have the same label names in
// their ConstLabels. // their ConstLabels.
// //
// Due to the way a Summary is represented in the Prometheus text format
// and how it is handled by the Prometheus server internally, “quantile”
// is an illegal label name. Construction of a Summary or SummaryVec
// will panic if this label name is used in ConstLabels.
//
// ConstLabels are only used rarely. In particular, do not use them to // ConstLabels are only used rarely. In particular, do not use them to
// attach the same labels to all your metrics. Those use cases are // attach the same labels to all your metrics. Those use cases are
// better covered by target labels set by the scraping Prometheus // better covered by target labels set by the scraping Prometheus
@ -402,7 +407,16 @@ type SummaryVec struct {
// NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and // NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
// partitioned by the given label names. // partitioned by the given label names.
//
// Due to the way a Summary is represented in the Prometheus text format and how
// it is handled by the Prometheus server internally, “quantile” is an illegal
// label name. NewSummaryVec will panic if this label name is used.
func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec { func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
for _, ln := range labelNames {
if ln == quantileLabel {
panic(errQuantileLabelNotAllowed)
}
}
desc := NewDesc( desc := NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
opts.Help, opts.Help,
@ -572,7 +586,7 @@ func (s *constSummary) Write(out *dto.Metric) error {
// map[float64]float64{0.5: 0.23, 0.99: 0.56} // map[float64]float64{0.5: 0.23, 0.99: 0.56}
// //
// NewConstSummary returns an error if the length of labelValues is not // NewConstSummary returns an error if the length of labelValues is not
// consistent with the variable labels in Desc. // consistent with the variable labels in Desc or if Desc is invalid.
func NewConstSummary( func NewConstSummary(
desc *Desc, desc *Desc,
count uint64, count uint64,
@ -580,6 +594,9 @@ func NewConstSummary(
quantiles map[float64]float64, quantiles map[float64]float64,
labelValues ...string, labelValues ...string,
) (Metric, error) { ) (Metric, error) {
if desc.err != nil {
return nil, desc.err
}
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil { if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
return nil, err return nil, err
} }

View file

@ -17,9 +17,9 @@ import (
"fmt" "fmt"
"sort" "sort"
dto "github.com/prometheus/client_model/go"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
) )
// ValueType is an enumeration of metric types that represent a simple value. // ValueType is an enumeration of metric types that represent a simple value.
@ -77,8 +77,12 @@ func (v *valueFunc) Write(out *dto.Metric) error {
// operations. However, when implementing custom Collectors, it is useful as a // operations. However, when implementing custom Collectors, it is useful as a
// throw-away metric that is generated on the fly to send it to Prometheus in // throw-away metric that is generated on the fly to send it to Prometheus in
// the Collect method. NewConstMetric returns an error if the length of // the Collect method. NewConstMetric returns an error if the length of
// labelValues is not consistent with the variable labels in Desc. // labelValues is not consistent with the variable labels in Desc or if Desc is
// invalid.
func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) { func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) {
if desc.err != nil {
return nil, desc.err
}
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil { if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
return nil, err return nil, err
} }
@ -153,6 +157,6 @@ func makeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
}) })
} }
labelPairs = append(labelPairs, desc.constLabelPairs...) labelPairs = append(labelPairs, desc.constLabelPairs...)
sort.Sort(LabelPairSorter(labelPairs)) sort.Sort(labelPairSorter(labelPairs))
return labelPairs return labelPairs
} }

View file

@ -0,0 +1,179 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus
import (
"fmt"
"sort"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
)
// WrapRegistererWith returns a Registerer wrapping the provided
// Registerer. Collectors registered with the returned Registerer will be
// registered with the wrapped Registerer in a modified way. The modified
// Collector adds the provided Labels to all Metrics it collects (as
// ConstLabels). The Metrics collected by the unmodified Collector must not
// duplicate any of those labels.
//
// WrapRegistererWith provides a way to add fixed labels to a subset of
// Collectors. It should not be used to add fixed labels to all metrics exposed.
//
// The Collector example demonstrates a use of WrapRegistererWith.
func WrapRegistererWith(labels Labels, reg Registerer) Registerer {
return &wrappingRegisterer{
wrappedRegisterer: reg,
labels: labels,
}
}
// WrapRegistererWithPrefix returns a Registerer wrapping the provided
// Registerer. Collectors registered with the returned Registerer will be
// registered with the wrapped Registerer in a modified way. The modified
// Collector adds the provided prefix to the name of all Metrics it collects.
//
// WrapRegistererWithPrefix is useful to have one place to prefix all metrics of
// a sub-system. To make this work, register metrics of the sub-system with the
// wrapping Registerer returned by WrapRegistererWithPrefix. It is rarely useful
// to use the same prefix for all metrics exposed. In particular, do not prefix
// metric names that are standardized across applications, as that would break
// horizontal monitoring, for example the metrics provided by the Go collector
// (see NewGoCollector) and the process collector (see NewProcessCollector). (In
// fact, those metrics are already prefixed with “go_” or “process_”,
// respectively.)
func WrapRegistererWithPrefix(prefix string, reg Registerer) Registerer {
return &wrappingRegisterer{
wrappedRegisterer: reg,
prefix: prefix,
}
}
type wrappingRegisterer struct {
wrappedRegisterer Registerer
prefix string
labels Labels
}
func (r *wrappingRegisterer) Register(c Collector) error {
return r.wrappedRegisterer.Register(&wrappingCollector{
wrappedCollector: c,
prefix: r.prefix,
labels: r.labels,
})
}
func (r *wrappingRegisterer) MustRegister(cs ...Collector) {
for _, c := range cs {
if err := r.Register(c); err != nil {
panic(err)
}
}
}
func (r *wrappingRegisterer) Unregister(c Collector) bool {
return r.wrappedRegisterer.Unregister(&wrappingCollector{
wrappedCollector: c,
prefix: r.prefix,
labels: r.labels,
})
}
type wrappingCollector struct {
wrappedCollector Collector
prefix string
labels Labels
}
func (c *wrappingCollector) Collect(ch chan<- Metric) {
wrappedCh := make(chan Metric)
go func() {
c.wrappedCollector.Collect(wrappedCh)
close(wrappedCh)
}()
for m := range wrappedCh {
ch <- &wrappingMetric{
wrappedMetric: m,
prefix: c.prefix,
labels: c.labels,
}
}
}
func (c *wrappingCollector) Describe(ch chan<- *Desc) {
wrappedCh := make(chan *Desc)
go func() {
c.wrappedCollector.Describe(wrappedCh)
close(wrappedCh)
}()
for desc := range wrappedCh {
ch <- wrapDesc(desc, c.prefix, c.labels)
}
}
type wrappingMetric struct {
wrappedMetric Metric
prefix string
labels Labels
}
func (m *wrappingMetric) Desc() *Desc {
return wrapDesc(m.wrappedMetric.Desc(), m.prefix, m.labels)
}
func (m *wrappingMetric) Write(out *dto.Metric) error {
if err := m.wrappedMetric.Write(out); err != nil {
return err
}
if len(m.labels) == 0 {
// No wrapping labels.
return nil
}
for ln, lv := range m.labels {
out.Label = append(out.Label, &dto.LabelPair{
Name: proto.String(ln),
Value: proto.String(lv),
})
}
sort.Sort(labelPairSorter(out.Label))
return nil
}
func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc {
constLabels := Labels{}
for _, lp := range desc.constLabelPairs {
constLabels[*lp.Name] = *lp.Value
}
for ln, lv := range labels {
if _, alreadyUsed := constLabels[ln]; alreadyUsed {
return &Desc{
fqName: desc.fqName,
help: desc.help,
variableLabels: desc.variableLabels,
constLabelPairs: desc.constLabelPairs,
err: fmt.Errorf("attempted wrapping with already existing label name %q", ln),
}
}
constLabels[ln] = lv
}
// NewDesc will do remaining validations.
newDesc := NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels)
// Propagate errors if there was any. This will override any errer
// created by NewDesc above, i.e. earlier errors get precedence.
if desc.err != nil {
newDesc.err = desc.err
}
return newDesc
}

View file

@ -1,34 +1,23 @@
// Code generated by protoc-gen-go. // Code generated by protoc-gen-go. DO NOT EDIT.
// source: metrics.proto // source: metrics.proto
// DO NOT EDIT!
/* package io_prometheus_client // import "github.com/prometheus/client_model/go"
Package io_prometheus_client is a generated protocol buffer package.
It is generated from these files:
metrics.proto
It has these top-level messages:
LabelPair
Gauge
Counter
Quantile
Summary
Untyped
Histogram
Bucket
Metric
MetricFamily
*/
package io_prometheus_client
import proto "github.com/golang/protobuf/proto" import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math" import math "math"
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type MetricType int32 type MetricType int32
const ( const (
@ -70,16 +59,41 @@ func (x *MetricType) UnmarshalJSON(data []byte) error {
*x = MetricType(value) *x = MetricType(value)
return nil return nil
} }
func (MetricType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{0}
}
type LabelPair struct { type LabelPair struct {
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
XXX_unrecognized []byte `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *LabelPair) Reset() { *m = LabelPair{} } func (m *LabelPair) Reset() { *m = LabelPair{} }
func (m *LabelPair) String() string { return proto.CompactTextString(m) } func (m *LabelPair) String() string { return proto.CompactTextString(m) }
func (*LabelPair) ProtoMessage() {} func (*LabelPair) ProtoMessage() {}
func (*LabelPair) Descriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{0}
}
func (m *LabelPair) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LabelPair.Unmarshal(m, b)
}
func (m *LabelPair) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_LabelPair.Marshal(b, m, deterministic)
}
func (dst *LabelPair) XXX_Merge(src proto.Message) {
xxx_messageInfo_LabelPair.Merge(dst, src)
}
func (m *LabelPair) XXX_Size() int {
return xxx_messageInfo_LabelPair.Size(m)
}
func (m *LabelPair) XXX_DiscardUnknown() {
xxx_messageInfo_LabelPair.DiscardUnknown(m)
}
var xxx_messageInfo_LabelPair proto.InternalMessageInfo
func (m *LabelPair) GetName() string { func (m *LabelPair) GetName() string {
if m != nil && m.Name != nil { if m != nil && m.Name != nil {
@ -96,13 +110,35 @@ func (m *LabelPair) GetValue() string {
} }
type Gauge struct { type Gauge struct {
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"` Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
XXX_unrecognized []byte `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *Gauge) Reset() { *m = Gauge{} } func (m *Gauge) Reset() { *m = Gauge{} }
func (m *Gauge) String() string { return proto.CompactTextString(m) } func (m *Gauge) String() string { return proto.CompactTextString(m) }
func (*Gauge) ProtoMessage() {} func (*Gauge) ProtoMessage() {}
func (*Gauge) Descriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{1}
}
func (m *Gauge) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Gauge.Unmarshal(m, b)
}
func (m *Gauge) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Gauge.Marshal(b, m, deterministic)
}
func (dst *Gauge) XXX_Merge(src proto.Message) {
xxx_messageInfo_Gauge.Merge(dst, src)
}
func (m *Gauge) XXX_Size() int {
return xxx_messageInfo_Gauge.Size(m)
}
func (m *Gauge) XXX_DiscardUnknown() {
xxx_messageInfo_Gauge.DiscardUnknown(m)
}
var xxx_messageInfo_Gauge proto.InternalMessageInfo
func (m *Gauge) GetValue() float64 { func (m *Gauge) GetValue() float64 {
if m != nil && m.Value != nil { if m != nil && m.Value != nil {
@ -112,13 +148,35 @@ func (m *Gauge) GetValue() float64 {
} }
type Counter struct { type Counter struct {
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"` Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
XXX_unrecognized []byte `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *Counter) Reset() { *m = Counter{} } func (m *Counter) Reset() { *m = Counter{} }
func (m *Counter) String() string { return proto.CompactTextString(m) } func (m *Counter) String() string { return proto.CompactTextString(m) }
func (*Counter) ProtoMessage() {} func (*Counter) ProtoMessage() {}
func (*Counter) Descriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{2}
}
func (m *Counter) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Counter.Unmarshal(m, b)
}
func (m *Counter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Counter.Marshal(b, m, deterministic)
}
func (dst *Counter) XXX_Merge(src proto.Message) {
xxx_messageInfo_Counter.Merge(dst, src)
}
func (m *Counter) XXX_Size() int {
return xxx_messageInfo_Counter.Size(m)
}
func (m *Counter) XXX_DiscardUnknown() {
xxx_messageInfo_Counter.DiscardUnknown(m)
}
var xxx_messageInfo_Counter proto.InternalMessageInfo
func (m *Counter) GetValue() float64 { func (m *Counter) GetValue() float64 {
if m != nil && m.Value != nil { if m != nil && m.Value != nil {
@ -128,14 +186,36 @@ func (m *Counter) GetValue() float64 {
} }
type Quantile struct { type Quantile struct {
Quantile *float64 `protobuf:"fixed64,1,opt,name=quantile" json:"quantile,omitempty"` Quantile *float64 `protobuf:"fixed64,1,opt,name=quantile" json:"quantile,omitempty"`
Value *float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"` Value *float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
XXX_unrecognized []byte `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *Quantile) Reset() { *m = Quantile{} } func (m *Quantile) Reset() { *m = Quantile{} }
func (m *Quantile) String() string { return proto.CompactTextString(m) } func (m *Quantile) String() string { return proto.CompactTextString(m) }
func (*Quantile) ProtoMessage() {} func (*Quantile) ProtoMessage() {}
func (*Quantile) Descriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{3}
}
func (m *Quantile) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Quantile.Unmarshal(m, b)
}
func (m *Quantile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Quantile.Marshal(b, m, deterministic)
}
func (dst *Quantile) XXX_Merge(src proto.Message) {
xxx_messageInfo_Quantile.Merge(dst, src)
}
func (m *Quantile) XXX_Size() int {
return xxx_messageInfo_Quantile.Size(m)
}
func (m *Quantile) XXX_DiscardUnknown() {
xxx_messageInfo_Quantile.DiscardUnknown(m)
}
var xxx_messageInfo_Quantile proto.InternalMessageInfo
func (m *Quantile) GetQuantile() float64 { func (m *Quantile) GetQuantile() float64 {
if m != nil && m.Quantile != nil { if m != nil && m.Quantile != nil {
@ -152,15 +232,37 @@ func (m *Quantile) GetValue() float64 {
} }
type Summary struct { type Summary struct {
SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count" json:"sample_count,omitempty"` SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count,json=sampleCount" json:"sample_count,omitempty"`
SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum" json:"sample_sum,omitempty"` SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum,json=sampleSum" json:"sample_sum,omitempty"`
Quantile []*Quantile `protobuf:"bytes,3,rep,name=quantile" json:"quantile,omitempty"` Quantile []*Quantile `protobuf:"bytes,3,rep,name=quantile" json:"quantile,omitempty"`
XXX_unrecognized []byte `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *Summary) Reset() { *m = Summary{} } func (m *Summary) Reset() { *m = Summary{} }
func (m *Summary) String() string { return proto.CompactTextString(m) } func (m *Summary) String() string { return proto.CompactTextString(m) }
func (*Summary) ProtoMessage() {} func (*Summary) ProtoMessage() {}
func (*Summary) Descriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{4}
}
func (m *Summary) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Summary.Unmarshal(m, b)
}
func (m *Summary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Summary.Marshal(b, m, deterministic)
}
func (dst *Summary) XXX_Merge(src proto.Message) {
xxx_messageInfo_Summary.Merge(dst, src)
}
func (m *Summary) XXX_Size() int {
return xxx_messageInfo_Summary.Size(m)
}
func (m *Summary) XXX_DiscardUnknown() {
xxx_messageInfo_Summary.DiscardUnknown(m)
}
var xxx_messageInfo_Summary proto.InternalMessageInfo
func (m *Summary) GetSampleCount() uint64 { func (m *Summary) GetSampleCount() uint64 {
if m != nil && m.SampleCount != nil { if m != nil && m.SampleCount != nil {
@ -184,13 +286,35 @@ func (m *Summary) GetQuantile() []*Quantile {
} }
type Untyped struct { type Untyped struct {
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"` Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
XXX_unrecognized []byte `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *Untyped) Reset() { *m = Untyped{} } func (m *Untyped) Reset() { *m = Untyped{} }
func (m *Untyped) String() string { return proto.CompactTextString(m) } func (m *Untyped) String() string { return proto.CompactTextString(m) }
func (*Untyped) ProtoMessage() {} func (*Untyped) ProtoMessage() {}
func (*Untyped) Descriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{5}
}
func (m *Untyped) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Untyped.Unmarshal(m, b)
}
func (m *Untyped) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Untyped.Marshal(b, m, deterministic)
}
func (dst *Untyped) XXX_Merge(src proto.Message) {
xxx_messageInfo_Untyped.Merge(dst, src)
}
func (m *Untyped) XXX_Size() int {
return xxx_messageInfo_Untyped.Size(m)
}
func (m *Untyped) XXX_DiscardUnknown() {
xxx_messageInfo_Untyped.DiscardUnknown(m)
}
var xxx_messageInfo_Untyped proto.InternalMessageInfo
func (m *Untyped) GetValue() float64 { func (m *Untyped) GetValue() float64 {
if m != nil && m.Value != nil { if m != nil && m.Value != nil {
@ -200,15 +324,37 @@ func (m *Untyped) GetValue() float64 {
} }
type Histogram struct { type Histogram struct {
SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count" json:"sample_count,omitempty"` SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count,json=sampleCount" json:"sample_count,omitempty"`
SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum" json:"sample_sum,omitempty"` SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum,json=sampleSum" json:"sample_sum,omitempty"`
Bucket []*Bucket `protobuf:"bytes,3,rep,name=bucket" json:"bucket,omitempty"` Bucket []*Bucket `protobuf:"bytes,3,rep,name=bucket" json:"bucket,omitempty"`
XXX_unrecognized []byte `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *Histogram) Reset() { *m = Histogram{} } func (m *Histogram) Reset() { *m = Histogram{} }
func (m *Histogram) String() string { return proto.CompactTextString(m) } func (m *Histogram) String() string { return proto.CompactTextString(m) }
func (*Histogram) ProtoMessage() {} func (*Histogram) ProtoMessage() {}
func (*Histogram) Descriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{6}
}
func (m *Histogram) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Histogram.Unmarshal(m, b)
}
func (m *Histogram) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Histogram.Marshal(b, m, deterministic)
}
func (dst *Histogram) XXX_Merge(src proto.Message) {
xxx_messageInfo_Histogram.Merge(dst, src)
}
func (m *Histogram) XXX_Size() int {
return xxx_messageInfo_Histogram.Size(m)
}
func (m *Histogram) XXX_DiscardUnknown() {
xxx_messageInfo_Histogram.DiscardUnknown(m)
}
var xxx_messageInfo_Histogram proto.InternalMessageInfo
func (m *Histogram) GetSampleCount() uint64 { func (m *Histogram) GetSampleCount() uint64 {
if m != nil && m.SampleCount != nil { if m != nil && m.SampleCount != nil {
@ -232,14 +378,36 @@ func (m *Histogram) GetBucket() []*Bucket {
} }
type Bucket struct { type Bucket struct {
CumulativeCount *uint64 `protobuf:"varint,1,opt,name=cumulative_count" json:"cumulative_count,omitempty"` CumulativeCount *uint64 `protobuf:"varint,1,opt,name=cumulative_count,json=cumulativeCount" json:"cumulative_count,omitempty"`
UpperBound *float64 `protobuf:"fixed64,2,opt,name=upper_bound" json:"upper_bound,omitempty"` UpperBound *float64 `protobuf:"fixed64,2,opt,name=upper_bound,json=upperBound" json:"upper_bound,omitempty"`
XXX_unrecognized []byte `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *Bucket) Reset() { *m = Bucket{} } func (m *Bucket) Reset() { *m = Bucket{} }
func (m *Bucket) String() string { return proto.CompactTextString(m) } func (m *Bucket) String() string { return proto.CompactTextString(m) }
func (*Bucket) ProtoMessage() {} func (*Bucket) ProtoMessage() {}
func (*Bucket) Descriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{7}
}
func (m *Bucket) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Bucket.Unmarshal(m, b)
}
func (m *Bucket) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Bucket.Marshal(b, m, deterministic)
}
func (dst *Bucket) XXX_Merge(src proto.Message) {
xxx_messageInfo_Bucket.Merge(dst, src)
}
func (m *Bucket) XXX_Size() int {
return xxx_messageInfo_Bucket.Size(m)
}
func (m *Bucket) XXX_DiscardUnknown() {
xxx_messageInfo_Bucket.DiscardUnknown(m)
}
var xxx_messageInfo_Bucket proto.InternalMessageInfo
func (m *Bucket) GetCumulativeCount() uint64 { func (m *Bucket) GetCumulativeCount() uint64 {
if m != nil && m.CumulativeCount != nil { if m != nil && m.CumulativeCount != nil {
@ -256,19 +424,41 @@ func (m *Bucket) GetUpperBound() float64 {
} }
type Metric struct { type Metric struct {
Label []*LabelPair `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"` Label []*LabelPair `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"`
Gauge *Gauge `protobuf:"bytes,2,opt,name=gauge" json:"gauge,omitempty"` Gauge *Gauge `protobuf:"bytes,2,opt,name=gauge" json:"gauge,omitempty"`
Counter *Counter `protobuf:"bytes,3,opt,name=counter" json:"counter,omitempty"` Counter *Counter `protobuf:"bytes,3,opt,name=counter" json:"counter,omitempty"`
Summary *Summary `protobuf:"bytes,4,opt,name=summary" json:"summary,omitempty"` Summary *Summary `protobuf:"bytes,4,opt,name=summary" json:"summary,omitempty"`
Untyped *Untyped `protobuf:"bytes,5,opt,name=untyped" json:"untyped,omitempty"` Untyped *Untyped `protobuf:"bytes,5,opt,name=untyped" json:"untyped,omitempty"`
Histogram *Histogram `protobuf:"bytes,7,opt,name=histogram" json:"histogram,omitempty"` Histogram *Histogram `protobuf:"bytes,7,opt,name=histogram" json:"histogram,omitempty"`
TimestampMs *int64 `protobuf:"varint,6,opt,name=timestamp_ms" json:"timestamp_ms,omitempty"` TimestampMs *int64 `protobuf:"varint,6,opt,name=timestamp_ms,json=timestampMs" json:"timestamp_ms,omitempty"`
XXX_unrecognized []byte `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *Metric) Reset() { *m = Metric{} } func (m *Metric) Reset() { *m = Metric{} }
func (m *Metric) String() string { return proto.CompactTextString(m) } func (m *Metric) String() string { return proto.CompactTextString(m) }
func (*Metric) ProtoMessage() {} func (*Metric) ProtoMessage() {}
func (*Metric) Descriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{8}
}
func (m *Metric) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Metric.Unmarshal(m, b)
}
func (m *Metric) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Metric.Marshal(b, m, deterministic)
}
func (dst *Metric) XXX_Merge(src proto.Message) {
xxx_messageInfo_Metric.Merge(dst, src)
}
func (m *Metric) XXX_Size() int {
return xxx_messageInfo_Metric.Size(m)
}
func (m *Metric) XXX_DiscardUnknown() {
xxx_messageInfo_Metric.DiscardUnknown(m)
}
var xxx_messageInfo_Metric proto.InternalMessageInfo
func (m *Metric) GetLabel() []*LabelPair { func (m *Metric) GetLabel() []*LabelPair {
if m != nil { if m != nil {
@ -320,16 +510,38 @@ func (m *Metric) GetTimestampMs() int64 {
} }
type MetricFamily struct { type MetricFamily struct {
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Help *string `protobuf:"bytes,2,opt,name=help" json:"help,omitempty"` Help *string `protobuf:"bytes,2,opt,name=help" json:"help,omitempty"`
Type *MetricType `protobuf:"varint,3,opt,name=type,enum=io.prometheus.client.MetricType" json:"type,omitempty"` Type *MetricType `protobuf:"varint,3,opt,name=type,enum=io.prometheus.client.MetricType" json:"type,omitempty"`
Metric []*Metric `protobuf:"bytes,4,rep,name=metric" json:"metric,omitempty"` Metric []*Metric `protobuf:"bytes,4,rep,name=metric" json:"metric,omitempty"`
XXX_unrecognized []byte `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *MetricFamily) Reset() { *m = MetricFamily{} } func (m *MetricFamily) Reset() { *m = MetricFamily{} }
func (m *MetricFamily) String() string { return proto.CompactTextString(m) } func (m *MetricFamily) String() string { return proto.CompactTextString(m) }
func (*MetricFamily) ProtoMessage() {} func (*MetricFamily) ProtoMessage() {}
func (*MetricFamily) Descriptor() ([]byte, []int) {
return fileDescriptor_metrics_c97c9a2b9560cb8f, []int{9}
}
func (m *MetricFamily) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_MetricFamily.Unmarshal(m, b)
}
func (m *MetricFamily) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_MetricFamily.Marshal(b, m, deterministic)
}
func (dst *MetricFamily) XXX_Merge(src proto.Message) {
xxx_messageInfo_MetricFamily.Merge(dst, src)
}
func (m *MetricFamily) XXX_Size() int {
return xxx_messageInfo_MetricFamily.Size(m)
}
func (m *MetricFamily) XXX_DiscardUnknown() {
xxx_messageInfo_MetricFamily.DiscardUnknown(m)
}
var xxx_messageInfo_MetricFamily proto.InternalMessageInfo
func (m *MetricFamily) GetName() string { func (m *MetricFamily) GetName() string {
if m != nil && m.Name != nil { if m != nil && m.Name != nil {
@ -360,5 +572,58 @@ func (m *MetricFamily) GetMetric() []*Metric {
} }
func init() { func init() {
proto.RegisterType((*LabelPair)(nil), "io.prometheus.client.LabelPair")
proto.RegisterType((*Gauge)(nil), "io.prometheus.client.Gauge")
proto.RegisterType((*Counter)(nil), "io.prometheus.client.Counter")
proto.RegisterType((*Quantile)(nil), "io.prometheus.client.Quantile")
proto.RegisterType((*Summary)(nil), "io.prometheus.client.Summary")
proto.RegisterType((*Untyped)(nil), "io.prometheus.client.Untyped")
proto.RegisterType((*Histogram)(nil), "io.prometheus.client.Histogram")
proto.RegisterType((*Bucket)(nil), "io.prometheus.client.Bucket")
proto.RegisterType((*Metric)(nil), "io.prometheus.client.Metric")
proto.RegisterType((*MetricFamily)(nil), "io.prometheus.client.MetricFamily")
proto.RegisterEnum("io.prometheus.client.MetricType", MetricType_name, MetricType_value) proto.RegisterEnum("io.prometheus.client.MetricType", MetricType_name, MetricType_value)
} }
func init() { proto.RegisterFile("metrics.proto", fileDescriptor_metrics_c97c9a2b9560cb8f) }
var fileDescriptor_metrics_c97c9a2b9560cb8f = []byte{
// 591 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x4f, 0x4f, 0xdb, 0x4e,
0x14, 0xfc, 0x99, 0xd8, 0x09, 0x7e, 0x86, 0x5f, 0xad, 0x15, 0x07, 0xab, 0x2d, 0x25, 0xcd, 0x89,
0xf6, 0x10, 0x54, 0x04, 0xaa, 0x44, 0xdb, 0x03, 0x50, 0x1a, 0x2a, 0xd5, 0x40, 0x37, 0xc9, 0x81,
0x5e, 0xac, 0x8d, 0x59, 0x25, 0x56, 0xbd, 0xb6, 0x6b, 0xef, 0x22, 0xe5, 0xdc, 0x43, 0xbf, 0x47,
0xbf, 0x68, 0xab, 0xfd, 0xe3, 0x18, 0x24, 0xc3, 0xa9, 0xb7, 0xb7, 0xf3, 0x66, 0xde, 0x8e, 0x77,
0xc7, 0x0b, 0x9b, 0x8c, 0xf2, 0x32, 0x89, 0xab, 0x61, 0x51, 0xe6, 0x3c, 0x47, 0x5b, 0x49, 0x2e,
0x2b, 0x46, 0xf9, 0x82, 0x8a, 0x6a, 0x18, 0xa7, 0x09, 0xcd, 0xf8, 0xe0, 0x10, 0xdc, 0x2f, 0x64,
0x46, 0xd3, 0x2b, 0x92, 0x94, 0x08, 0x81, 0x9d, 0x11, 0x46, 0x03, 0xab, 0x6f, 0xed, 0xba, 0x58,
0xd5, 0x68, 0x0b, 0x9c, 0x5b, 0x92, 0x0a, 0x1a, 0xac, 0x29, 0x50, 0x2f, 0x06, 0xdb, 0xe0, 0x8c,
0x88, 0x98, 0xdf, 0x69, 0x4b, 0x8d, 0x55, 0xb7, 0x77, 0xa0, 0x77, 0x9a, 0x8b, 0x8c, 0xd3, 0xf2,
0x01, 0xc2, 0x7b, 0x58, 0xff, 0x2a, 0x48, 0xc6, 0x93, 0x94, 0xa2, 0xa7, 0xb0, 0xfe, 0xc3, 0xd4,
0x86, 0xb4, 0x5a, 0xdf, 0xdf, 0x7d, 0xa5, 0xfe, 0x65, 0x41, 0x6f, 0x2c, 0x18, 0x23, 0xe5, 0x12,
0xbd, 0x84, 0x8d, 0x8a, 0xb0, 0x22, 0xa5, 0x51, 0x2c, 0x77, 0x54, 0x13, 0x6c, 0xec, 0x69, 0x4c,
0x99, 0x40, 0xdb, 0x00, 0x86, 0x52, 0x09, 0x66, 0x26, 0xb9, 0x1a, 0x19, 0x0b, 0x86, 0x8e, 0xee,
0xec, 0xdf, 0xe9, 0x77, 0x76, 0xbd, 0xfd, 0x17, 0xc3, 0xb6, 0xb3, 0x1a, 0xd6, 0x8e, 0x1b, 0x7f,
0xf2, 0x43, 0xa7, 0x19, 0x5f, 0x16, 0xf4, 0xe6, 0x81, 0x0f, 0xfd, 0x69, 0x81, 0x7b, 0x9e, 0x54,
0x3c, 0x9f, 0x97, 0x84, 0xfd, 0x03, 0xb3, 0x07, 0xd0, 0x9d, 0x89, 0xf8, 0x3b, 0xe5, 0xc6, 0xea,
0xf3, 0x76, 0xab, 0x27, 0x8a, 0x83, 0x0d, 0x77, 0x30, 0x81, 0xae, 0x46, 0xd0, 0x2b, 0xf0, 0x63,
0xc1, 0x44, 0x4a, 0x78, 0x72, 0x7b, 0xdf, 0xc5, 0x93, 0x06, 0xd7, 0x4e, 0x76, 0xc0, 0x13, 0x45,
0x41, 0xcb, 0x68, 0x96, 0x8b, 0xec, 0xc6, 0x58, 0x01, 0x05, 0x9d, 0x48, 0x64, 0xf0, 0x67, 0x0d,
0xba, 0xa1, 0xca, 0x18, 0x3a, 0x04, 0x27, 0x95, 0x31, 0x0a, 0x2c, 0xe5, 0x6a, 0xa7, 0xdd, 0xd5,
0x2a, 0x69, 0x58, 0xb3, 0xd1, 0x1b, 0x70, 0xe6, 0x32, 0x46, 0x6a, 0xb8, 0xb7, 0xff, 0xac, 0x5d,
0xa6, 0x92, 0x86, 0x35, 0x13, 0xbd, 0x85, 0x5e, 0xac, 0xa3, 0x15, 0x74, 0x94, 0x68, 0xbb, 0x5d,
0x64, 0xf2, 0x87, 0x6b, 0xb6, 0x14, 0x56, 0x3a, 0x33, 0x81, 0xfd, 0x98, 0xd0, 0x04, 0x0b, 0xd7,
0x6c, 0x29, 0x14, 0xfa, 0x8e, 0x03, 0xe7, 0x31, 0xa1, 0x09, 0x02, 0xae, 0xd9, 0xe8, 0x03, 0xb8,
0x8b, 0xfa, 0xea, 0x83, 0x9e, 0x92, 0x3e, 0x70, 0x30, 0xab, 0x84, 0xe0, 0x46, 0x21, 0xc3, 0xc2,
0x13, 0x46, 0x2b, 0x4e, 0x58, 0x11, 0xb1, 0x2a, 0xe8, 0xf6, 0xad, 0xdd, 0x0e, 0xf6, 0x56, 0x58,
0x58, 0x0d, 0x7e, 0x5b, 0xb0, 0xa1, 0x6f, 0xe0, 0x13, 0x61, 0x49, 0xba, 0x6c, 0xfd, 0x83, 0x11,
0xd8, 0x0b, 0x9a, 0x16, 0xe6, 0x07, 0x56, 0x35, 0x3a, 0x00, 0x5b, 0x7a, 0x54, 0x47, 0xf8, 0xff,
0x7e, 0xbf, 0xdd, 0x95, 0x9e, 0x3c, 0x59, 0x16, 0x14, 0x2b, 0xb6, 0x0c, 0x9f, 0x7e, 0x53, 0x02,
0xfb, 0xb1, 0xf0, 0x69, 0x1d, 0x36, 0xdc, 0xd7, 0x21, 0x40, 0x33, 0x09, 0x79, 0xd0, 0x3b, 0xbd,
0x9c, 0x5e, 0x4c, 0xce, 0xb0, 0xff, 0x1f, 0x72, 0xc1, 0x19, 0x1d, 0x4f, 0x47, 0x67, 0xbe, 0x25,
0xf1, 0xf1, 0x34, 0x0c, 0x8f, 0xf1, 0xb5, 0xbf, 0x26, 0x17, 0xd3, 0x8b, 0xc9, 0xf5, 0xd5, 0xd9,
0x47, 0xbf, 0x83, 0x36, 0xc1, 0x3d, 0xff, 0x3c, 0x9e, 0x5c, 0x8e, 0xf0, 0x71, 0xe8, 0xdb, 0x27,
0x18, 0x5a, 0x5f, 0xb2, 0x6f, 0x47, 0xf3, 0x84, 0x2f, 0xc4, 0x6c, 0x18, 0xe7, 0x6c, 0xaf, 0xe9,
0xee, 0xe9, 0x6e, 0xc4, 0xf2, 0x1b, 0x9a, 0xee, 0xcd, 0xf3, 0x77, 0x49, 0x1e, 0x35, 0xdd, 0x48,
0x77, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x45, 0x21, 0x7f, 0x64, 0x2b, 0x05, 0x00, 0x00,
}

34
vendor/vendor.json vendored
View file

@ -757,34 +757,40 @@
"revisionTime": "2016-06-15T09:26:46Z" "revisionTime": "2016-06-15T09:26:46Z"
}, },
{ {
"checksumSHA1": "4VppKBbzCSmoFfQxGNUm0TYiFCA=", "checksumSHA1": "qeRNcE15+58PvlfKwBA2n96Qa5I=",
"path": "github.com/prometheus/client_golang/api", "path": "github.com/prometheus/client_golang/api",
"revision": "faf4ec335fe01ae5a6a0eaa34a5a9333bfbd1a30", "revision": "e637cec7d9c8990247098639ebc6d43dd34ddd49",
"revisionTime": "2018-06-07T12:36:07Z" "revisionTime": "2018-09-17T10:21:22Z"
}, },
{ {
"checksumSHA1": "83pGB3cRG5uqx9O5d+7MCB+TFT4=", "checksumSHA1": "83pGB3cRG5uqx9O5d+7MCB+TFT4=",
"path": "github.com/prometheus/client_golang/api/prometheus/v1", "path": "github.com/prometheus/client_golang/api/prometheus/v1",
"revision": "faf4ec335fe01ae5a6a0eaa34a5a9333bfbd1a30", "revision": "e637cec7d9c8990247098639ebc6d43dd34ddd49",
"revisionTime": "2018-06-07T12:36:07Z" "revisionTime": "2018-09-17T10:21:22Z"
}, },
{ {
"checksumSHA1": "WVgL9pNO2RZCCcaXfSYSNEPgtCo=", "checksumSHA1": "lLvg5TpUtFbkyAoh+aI5T/nnpWw=",
"path": "github.com/prometheus/client_golang/prometheus", "path": "github.com/prometheus/client_golang/prometheus",
"revision": "faf4ec335fe01ae5a6a0eaa34a5a9333bfbd1a30", "revision": "e637cec7d9c8990247098639ebc6d43dd34ddd49",
"revisionTime": "2018-06-07T12:36:07Z" "revisionTime": "2018-09-17T10:21:22Z"
}, },
{ {
"checksumSHA1": "MYqKV5uVTfCxP9zBug7naBQ1vr8=", "checksumSHA1": "UBqhkyjCz47+S19MVTigxJ2VjVQ=",
"path": "github.com/prometheus/client_golang/prometheus/internal",
"revision": "e637cec7d9c8990247098639ebc6d43dd34ddd49",
"revisionTime": "2018-09-17T10:21:22Z"
},
{
"checksumSHA1": "d5BiEvD8MrgpWQ6PQJUvawJsMak=",
"path": "github.com/prometheus/client_golang/prometheus/promhttp", "path": "github.com/prometheus/client_golang/prometheus/promhttp",
"revision": "faf4ec335fe01ae5a6a0eaa34a5a9333bfbd1a30", "revision": "e637cec7d9c8990247098639ebc6d43dd34ddd49",
"revisionTime": "2018-06-07T12:36:07Z" "revisionTime": "2018-09-17T10:21:22Z"
}, },
{ {
"checksumSHA1": "DvwvOlPNAgRntBzt3b3OSRMS2N4=", "checksumSHA1": "V8xkqgmP66sq2ZW4QO5wi9a4oZE=",
"path": "github.com/prometheus/client_model/go", "path": "github.com/prometheus/client_model/go",
"revision": "fa8ad6fec33561be4280a8f0514318c79d7f6cb6", "revision": "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f",
"revisionTime": "2015-02-12T10:17:44Z" "revisionTime": "2018-07-12T10:51:10Z"
}, },
{ {
"checksumSHA1": "cxLURB7wEFsMAZ6fi5Ptfdrwdc8=", "checksumSHA1": "cxLURB7wEFsMAZ6fi5Ptfdrwdc8=",