metrics: upgraded prometheus http client to 0.9.4 to address label conflict in Nomad 0.9.x reported in #5345

This commit is contained in:
Chris Baker 2019-06-18 18:21:21 +00:00
parent 3b8315f381
commit 50b491564d
55 changed files with 6275 additions and 1595 deletions

View file

@ -0,0 +1,29 @@
// Copyright 2019 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.12
package prometheus
import "runtime/debug"
// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go 1.12+.
func readBuildInfo() (path, version, sum string) {
path, version, sum = "unknown", "unknown", "unknown"
if bi, ok := debug.ReadBuildInfo(); ok {
path = bi.Main.Path
version = bi.Main.Version
sum = bi.Main.Sum
}
return
}

View file

@ -0,0 +1,22 @@
// Copyright 2019 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !go1.12
package prometheus
// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go versions before
// 1.12. Remove this whole file once the minimum supported Go version is 1.12.
func readBuildInfo() (path, version, sum string) {
return "unknown", "unknown", "unknown"
}

View file

@ -29,27 +29,72 @@ 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. It may be called concurrently and
// therefore must be implemented in a concurrency safe way.
//
// 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 Collector (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

@ -15,6 +15,10 @@ package prometheus
import ( import (
"errors" "errors"
"math"
"sync/atomic"
dto "github.com/prometheus/client_model/go"
) )
// Counter is a Metric that represents a single numerical value that only ever // Counter is a Metric that represents a single numerical value that only ever
@ -42,6 +46,14 @@ type Counter interface {
type CounterOpts Opts type CounterOpts Opts
// NewCounter creates a new Counter based on the provided CounterOpts. // NewCounter creates a new Counter based on the provided CounterOpts.
//
// The returned implementation tracks the counter value in two separate
// variables, a float64 and a uint64. The latter is used to track calls of the
// Inc method and calls of the Add method with a value that can be represented
// as a uint64. This allows atomic increments of the counter with optimal
// performance. (It is common to have an Inc call in very hot execution paths.)
// Both internal tracking values are added up in the Write method. This has to
// be taken into account when it comes to precision and overflow behavior.
func NewCounter(opts CounterOpts) Counter { func NewCounter(opts CounterOpts) Counter {
desc := NewDesc( desc := NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
@ -49,20 +61,58 @@ func NewCounter(opts CounterOpts) Counter {
nil, nil,
opts.ConstLabels, opts.ConstLabels,
) )
result := &counter{value: value{desc: desc, valType: CounterValue, labelPairs: desc.constLabelPairs}} result := &counter{desc: desc, labelPairs: desc.constLabelPairs}
result.init(result) // Init self-collection. result.init(result) // Init self-collection.
return result return result
} }
type counter struct { type counter struct {
value // valBits contains the bits of the represented float64 value, while
// valInt stores values that are exact integers. Both have to go first
// in the struct to guarantee alignment for atomic operations.
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
valBits uint64
valInt uint64
selfCollector
desc *Desc
labelPairs []*dto.LabelPair
}
func (c *counter) Desc() *Desc {
return c.desc
} }
func (c *counter) Add(v float64) { func (c *counter) Add(v float64) {
if v < 0 { if v < 0 {
panic(errors.New("counter cannot decrease in value")) panic(errors.New("counter cannot decrease in value"))
} }
c.value.Add(v) ival := uint64(v)
if float64(ival) == v {
atomic.AddUint64(&c.valInt, ival)
return
}
for {
oldBits := atomic.LoadUint64(&c.valBits)
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
if atomic.CompareAndSwapUint64(&c.valBits, oldBits, newBits) {
return
}
}
}
func (c *counter) Inc() {
atomic.AddUint64(&c.valInt, 1)
}
func (c *counter) Write(out *dto.Metric) error {
fval := math.Float64frombits(atomic.LoadUint64(&c.valBits))
ival := atomic.LoadUint64(&c.valInt)
val := fval + float64(ival)
return populateMetric(CounterValue, val, c.labelPairs, out)
} }
// CounterVec is a Collector that bundles a set of Counters that all share the // CounterVec is a Collector that bundles a set of Counters that all share the
@ -85,11 +135,10 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
) )
return &CounterVec{ return &CounterVec{
metricVec: newMetricVec(desc, func(lvs ...string) Metric { metricVec: newMetricVec(desc, func(lvs ...string) Metric {
result := &counter{value: value{ if len(lvs) != len(desc.variableLabels) {
desc: desc, panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
valType: CounterValue, }
labelPairs: makeLabelPairs(desc, lvs), result := &counter{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
}}
result.init(result) // Init self-collection. result.init(result) // Init self-collection.
return result return result
}), }),
@ -111,7 +160,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
// Counter with the same label values is created later. // Counter with the same label values is created later.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of VariableLabels in Desc. // number of VariableLabels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
@ -119,8 +168,8 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
// latter has a much more readable (albeit more verbose) syntax, but it comes // latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the GaugeVec example. // See also the GaugeVec example.
func (m *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) { func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
metric, err := m.metricVec.getMetricWithLabelValues(lvs...) metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Counter), err return metric.(Counter), err
} }
@ -134,13 +183,13 @@ func (m *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
// the same as for GetMetricWithLabelValues. // the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc. // with those of the VariableLabels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (m *CounterVec) GetMetricWith(labels Labels) (Counter, error) { func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
metric, err := m.metricVec.getMetricWith(labels) metric, err := v.metricVec.getMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Counter), err return metric.(Counter), err
} }
@ -148,18 +197,57 @@ func (m *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
} }
// WithLabelValues works as GetMetricWithLabelValues, but panics where // WithLabelValues works as GetMetricWithLabelValues, but panics where
// GetMetricWithLabelValues would have returned an error. By not returning an // GetMetricWithLabelValues would have returned an error. Not returning an
// error, WithLabelValues allows shortcuts like // error allows shortcuts like
// myVec.WithLabelValues("404", "GET").Add(42) // myVec.WithLabelValues("404", "GET").Add(42)
func (m *CounterVec) WithLabelValues(lvs ...string) Counter { func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
return m.metricVec.withLabelValues(lvs...).(Counter) c, err := v.GetMetricWithLabelValues(lvs...)
if err != nil {
panic(err)
}
return c
} }
// With works as GetMetricWith, but panics where GetMetricWithLabels would have // With works as GetMetricWith, but panics where GetMetricWithLabels would have
// returned an error. By not returning an error, With allows shortcuts like // returned an error. Not returning an error allows shortcuts like
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42) // myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
func (m *CounterVec) With(labels Labels) Counter { func (v *CounterVec) With(labels Labels) Counter {
return m.metricVec.with(labels).(Counter) c, err := v.GetMetricWith(labels)
if err != nil {
panic(err)
}
return c
}
// CurryWith returns a vector curried with the provided labels, i.e. the
// returned vector has those labels pre-set for all labeled operations performed
// on it. The cardinality of the curried vector is reduced accordingly. The
// order of the remaining labels stays the same (just with the curried labels
// taken out of the sequence which is relevant for the
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
// vector, but only with labels not yet used for currying before.
//
// The metrics contained in the CounterVec are shared between the curried and
// uncurried vectors. They are just accessed differently. Curried and uncurried
// vectors behave identically in terms of collection. Only one must be
// registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector.
func (v *CounterVec) CurryWith(labels Labels) (*CounterVec, error) {
vec, err := v.curryWith(labels)
if vec != nil {
return &CounterVec{vec}, err
}
return nil, err
}
// MustCurryWith works as CurryWith but panics where CurryWith would have
// returned an error.
func (v *CounterVec) MustCurryWith(labels Labels) *CounterVec {
vec, err := v.CurryWith(labels)
if err != nil {
panic(err)
}
return vec
} }
// CounterFunc is a Counter whose value is determined at collect time by calling a // CounterFunc is a Counter whose value is determined at collect time by calling a

View file

@ -67,24 +67,19 @@ 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.)
// //
// For constLabels, the label values are constant. Therefore, they are fully // For constLabels, the label values are constant. Therefore, they are fully
// specified in the Desc. See the Opts documentation for the implications of // specified in the Desc. See the Collector example for a usage pattern.
// constant labels.
func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc { func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc {
d := &Desc{ d := &Desc{
fqName: fqName, fqName: fqName,
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
@ -98,7 +93,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
// First add only the const label names and sort them... // First add only the const label names and sort them...
for labelName := range constLabels { for labelName := range constLabels {
if !checkLabelName(labelName) { if !checkLabelName(labelName) {
d.err = fmt.Errorf("%q is not a valid label name", labelName) d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName)
return d return d
} }
labelNames = append(labelNames, labelName) labelNames = append(labelNames, labelName)
@ -120,7 +115,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
// dimension with a different mix between preset and variable labels. // dimension with a different mix between preset and variable labels.
for _, labelName := range variableLabels { for _, labelName := range variableLabels {
if !checkLabelName(labelName) { if !checkLabelName(labelName) {
d.err = fmt.Errorf("%q is not a valid label name", labelName) d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName)
return d return d
} }
labelNames = append(labelNames, "$"+labelName) labelNames = append(labelNames, "$"+labelName)
@ -157,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

@ -11,10 +11,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package prometheus provides metrics primitives to instrument code for // Package prometheus is the core instrumentation package. It provides metrics
// monitoring. It also offers a registry for metrics. Sub-packages allow to // primitives to instrument code for monitoring. It also offers a registry for
// expose the registered metrics via HTTP (package promhttp) or push them to a // metrics. Sub-packages allow to expose the registered metrics via HTTP
// Pushgateway (package push). // (package promhttp) or push them to a Pushgateway (package push). There is
// also a sub-package promauto, which provides metrics constructors with
// automatic registration.
// //
// All exported functions and methods are safe to be used concurrently unless // All exported functions and methods are safe to be used concurrently unless
// specified otherwise. // specified otherwise.
@ -72,7 +74,10 @@
// The number of exported identifiers in this package might appear a bit // The number of exported identifiers in this package might appear a bit
// overwhelming. However, in addition to the basic plumbing shown in the example // overwhelming. However, in addition to the basic plumbing shown in the example
// above, you only need to understand the different metric types and their // above, you only need to understand the different metric types and their
// vector versions for basic usage. // vector versions for basic usage. Furthermore, if you are not concerned with
// fine-grained control of when and how to register metrics with the registry,
// have a look at the promauto package, which will effectively allow you to
// ignore registration altogether in simple cases.
// //
// Above, you have already touched the Counter and the Gauge. There are two more // Above, you have already touched the Counter and the Gauge. There are two more
// advanced metric types: the Summary and Histogram. A more thorough description // advanced metric types: the Summary and Histogram. A more thorough description
@ -116,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 mark the Collector “unchecked”. No
// checks are performed 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 situation 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

@ -13,6 +13,14 @@
package prometheus package prometheus
import (
"math"
"sync/atomic"
"time"
dto "github.com/prometheus/client_model/go"
)
// Gauge is a Metric that represents a single numerical value that can // Gauge is a Metric that represents a single numerical value that can
// arbitrarily go up and down. // arbitrarily go up and down.
// //
@ -48,13 +56,74 @@ type Gauge interface {
type GaugeOpts Opts type GaugeOpts Opts
// NewGauge creates a new Gauge based on the provided GaugeOpts. // NewGauge creates a new Gauge based on the provided GaugeOpts.
//
// The returned implementation is optimized for a fast Set method. If you have a
// choice for managing the value of a Gauge via Set vs. Inc/Dec/Add/Sub, pick
// the former. For example, the Inc method of the returned Gauge is slower than
// the Inc method of a Counter returned by NewCounter. This matches the typical
// scenarios for Gauges and Counters, where the former tends to be Set-heavy and
// the latter Inc-heavy.
func NewGauge(opts GaugeOpts) Gauge { func NewGauge(opts GaugeOpts) Gauge {
return newValue(NewDesc( desc := NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
opts.Help, opts.Help,
nil, nil,
opts.ConstLabels, opts.ConstLabels,
), GaugeValue, 0) )
result := &gauge{desc: desc, labelPairs: desc.constLabelPairs}
result.init(result) // Init self-collection.
return result
}
type gauge struct {
// valBits contains the bits of the represented float64 value. It has
// to go first in the struct to guarantee alignment for atomic
// operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
valBits uint64
selfCollector
desc *Desc
labelPairs []*dto.LabelPair
}
func (g *gauge) Desc() *Desc {
return g.desc
}
func (g *gauge) Set(val float64) {
atomic.StoreUint64(&g.valBits, math.Float64bits(val))
}
func (g *gauge) SetToCurrentTime() {
g.Set(float64(time.Now().UnixNano()) / 1e9)
}
func (g *gauge) Inc() {
g.Add(1)
}
func (g *gauge) Dec() {
g.Add(-1)
}
func (g *gauge) Add(val float64) {
for {
oldBits := atomic.LoadUint64(&g.valBits)
newBits := math.Float64bits(math.Float64frombits(oldBits) + val)
if atomic.CompareAndSwapUint64(&g.valBits, oldBits, newBits) {
return
}
}
}
func (g *gauge) Sub(val float64) {
g.Add(val * -1)
}
func (g *gauge) Write(out *dto.Metric) error {
val := math.Float64frombits(atomic.LoadUint64(&g.valBits))
return populateMetric(GaugeValue, val, g.labelPairs, out)
} }
// GaugeVec is a Collector that bundles a set of Gauges that all share the same // GaugeVec is a Collector that bundles a set of Gauges that all share the same
@ -77,7 +146,12 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
) )
return &GaugeVec{ return &GaugeVec{
metricVec: newMetricVec(desc, func(lvs ...string) Metric { metricVec: newMetricVec(desc, func(lvs ...string) Metric {
return newValue(desc, GaugeValue, 0, lvs...) if len(lvs) != len(desc.variableLabels) {
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
}
result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
result.init(result) // Init self-collection.
return result
}), }),
} }
} }
@ -98,15 +172,15 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
// example. // example.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of VariableLabels in Desc. // number of VariableLabels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
// an alternative to avoid that type of mistake. For higher label numbers, the // an alternative to avoid that type of mistake. For higher label numbers, the
// latter has a much more readable (albeit more verbose) syntax, but it comes // latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
func (m *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) { func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
metric, err := m.metricVec.getMetricWithLabelValues(lvs...) metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Gauge), err return metric.(Gauge), err
} }
@ -120,13 +194,13 @@ func (m *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
// the same as for GetMetricWithLabelValues. // the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc. // with those of the VariableLabels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (m *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) { func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
metric, err := m.metricVec.getMetricWith(labels) metric, err := v.metricVec.getMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Gauge), err return metric.(Gauge), err
} }
@ -134,18 +208,57 @@ func (m *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
} }
// WithLabelValues works as GetMetricWithLabelValues, but panics where // WithLabelValues works as GetMetricWithLabelValues, but panics where
// GetMetricWithLabelValues would have returned an error. By not returning an // GetMetricWithLabelValues would have returned an error. Not returning an
// error, WithLabelValues allows shortcuts like // error allows shortcuts like
// myVec.WithLabelValues("404", "GET").Add(42) // myVec.WithLabelValues("404", "GET").Add(42)
func (m *GaugeVec) WithLabelValues(lvs ...string) Gauge { func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
return m.metricVec.withLabelValues(lvs...).(Gauge) g, err := v.GetMetricWithLabelValues(lvs...)
if err != nil {
panic(err)
}
return g
} }
// With works as GetMetricWith, but panics where GetMetricWithLabels would have // With works as GetMetricWith, but panics where GetMetricWithLabels would have
// returned an error. By not returning an error, With allows shortcuts like // returned an error. Not returning an error allows shortcuts like
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42) // myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
func (m *GaugeVec) With(labels Labels) Gauge { func (v *GaugeVec) With(labels Labels) Gauge {
return m.metricVec.with(labels).(Gauge) g, err := v.GetMetricWith(labels)
if err != nil {
panic(err)
}
return g
}
// CurryWith returns a vector curried with the provided labels, i.e. the
// returned vector has those labels pre-set for all labeled operations performed
// on it. The cardinality of the curried vector is reduced accordingly. The
// order of the remaining labels stays the same (just with the curried labels
// taken out of the sequence which is relevant for the
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
// vector, but only with labels not yet used for currying before.
//
// The metrics contained in the GaugeVec are shared between the curried and
// uncurried vectors. They are just accessed differently. Curried and uncurried
// vectors behave identically in terms of collection. Only one must be
// registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector.
func (v *GaugeVec) CurryWith(labels Labels) (*GaugeVec, error) {
vec, err := v.curryWith(labels)
if vec != nil {
return &GaugeVec{vec}, err
}
return nil, err
}
// MustCurryWith works as CurryWith but panics where CurryWith would have
// returned an error.
func (v *GaugeVec) MustCurryWith(labels Labels) *GaugeVec {
vec, err := v.CurryWith(labels)
if err != nil {
panic(err)
}
return vec
} }
// GaugeFunc is a Gauge whose value is determined at collect time by calling a // GaugeFunc is a Gauge whose value is determined at collect time by calling a

View file

@ -1,9 +1,22 @@
// 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 (
"fmt"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"sync"
"time" "time"
) )
@ -13,12 +26,41 @@ type goCollector struct {
gcDesc *Desc gcDesc *Desc
goInfoDesc *Desc goInfoDesc *Desc
// metrics to describe and collect // ms... are memstats related.
metrics memStatsMetrics msLast *runtime.MemStats // Previously collected memstats.
msLastTimestamp time.Time
msMtx sync.Mutex // Protects msLast and msLastTimestamp.
msMetrics memStatsMetrics
msRead func(*runtime.MemStats) // For mocking in tests.
msMaxWait time.Duration // Wait time for fresh memstats.
msMaxAge time.Duration // Maximum allowed age of old memstats.
} }
// NewGoCollector returns a collector which exports metrics about the current // NewGoCollector returns a collector that exports metrics about the current Go
// go process. // process. This includes memory stats. To collect those, runtime.ReadMemStats
// is called. This requires to “stop the world”, which usually only happens for
// garbage collection (GC). Take the following implications into account when
// deciding whether to use the Go collector:
//
// 1. The performance impact of stopping the world is the more relevant the more
// frequently metrics are collected. However, with Go1.9 or later the
// stop-the-world time per metrics collection is very short (~25µs) so that the
// performance impact will only matter in rare cases. However, with older Go
// versions, the stop-the-world duration depends on the heap size and can be
// quite significant (~1.7 ms/GiB as per
// https://go-review.googlesource.com/c/go/+/34937).
//
// 2. During an ongoing GC, nothing else can stop the world. Therefore, if the
// metrics collection happens to coincide with GC, it will only complete after
// GC has finished. Usually, GC is fast enough to not cause problems. However,
// with a very large heap, GC might take multiple seconds, which is enough to
// cause scrape timeouts in common setups. To avoid this problem, the Go
// collector will use the memstats from a previous collection if
// runtime.ReadMemStats takes more than 1s. However, if there are no previously
// collected memstats, or their collection is more than 5m ago, the collection
// will block until runtime.ReadMemStats succeeds. (The problem might be solved
// in Go1.13, see https://github.com/golang/go/issues/19812 for the related Go
// issue.)
func NewGoCollector() Collector { func NewGoCollector() Collector {
return &goCollector{ return &goCollector{
goroutinesDesc: NewDesc( goroutinesDesc: NewDesc(
@ -37,7 +79,11 @@ func NewGoCollector() Collector {
"go_info", "go_info",
"Information about the Go environment.", "Information about the Go environment.",
nil, Labels{"version": runtime.Version()}), nil, Labels{"version": runtime.Version()}),
metrics: memStatsMetrics{ msLast: &runtime.MemStats{},
msRead: runtime.ReadMemStats,
msMaxWait: time.Second,
msMaxAge: 5 * time.Minute,
msMetrics: memStatsMetrics{
{ {
desc: NewDesc( desc: NewDesc(
memstatNamespace("alloc_bytes"), memstatNamespace("alloc_bytes"),
@ -236,7 +282,7 @@ func NewGoCollector() Collector {
} }
func memstatNamespace(s string) string { func memstatNamespace(s string) string {
return fmt.Sprintf("go_memstats_%s", s) return "go_memstats_" + s
} }
// Describe returns all descriptions of the collector. // Describe returns all descriptions of the collector.
@ -245,13 +291,27 @@ func (c *goCollector) Describe(ch chan<- *Desc) {
ch <- c.threadsDesc ch <- c.threadsDesc
ch <- c.gcDesc ch <- c.gcDesc
ch <- c.goInfoDesc ch <- c.goInfoDesc
for _, i := range c.metrics { for _, i := range c.msMetrics {
ch <- i.desc ch <- i.desc
} }
} }
// Collect returns the current state of all metrics of the collector. // Collect returns the current state of all metrics of the collector.
func (c *goCollector) Collect(ch chan<- Metric) { func (c *goCollector) Collect(ch chan<- Metric) {
var (
ms = &runtime.MemStats{}
done = make(chan struct{})
)
// Start reading memstats first as it might take a while.
go func() {
c.msRead(ms)
c.msMtx.Lock()
c.msLast = ms
c.msLastTimestamp = time.Now()
c.msMtx.Unlock()
close(done)
}()
ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine())) ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine()))
n, _ := runtime.ThreadCreateProfile(nil) n, _ := runtime.ThreadCreateProfile(nil)
ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, float64(n)) ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, float64(n))
@ -265,13 +325,35 @@ func (c *goCollector) Collect(ch chan<- Metric) {
quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds() quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds()
} }
quantiles[0.0] = stats.PauseQuantiles[0].Seconds() quantiles[0.0] = stats.PauseQuantiles[0].Seconds()
ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), quantiles) ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), stats.PauseTotal.Seconds(), quantiles)
ch <- MustNewConstMetric(c.goInfoDesc, GaugeValue, 1) ch <- MustNewConstMetric(c.goInfoDesc, GaugeValue, 1)
ms := &runtime.MemStats{} timer := time.NewTimer(c.msMaxWait)
runtime.ReadMemStats(ms) select {
for _, i := range c.metrics { case <-done: // Our own ReadMemStats succeeded in time. Use it.
timer.Stop() // Important for high collection frequencies to not pile up timers.
c.msCollect(ch, ms)
return
case <-timer.C: // Time out, use last memstats if possible. Continue below.
}
c.msMtx.Lock()
if time.Since(c.msLastTimestamp) < c.msMaxAge {
// Last memstats are recent enough. Collect from them under the lock.
c.msCollect(ch, c.msLast)
c.msMtx.Unlock()
return
}
// If we are here, the last memstats are too old or don't exist. We have
// to wait until our own ReadMemStats finally completes. For that to
// happen, we have to release the lock.
c.msMtx.Unlock()
<-done
c.msCollect(ch, ms)
}
func (c *goCollector) msCollect(ch chan<- Metric, ms *runtime.MemStats) {
for _, i := range c.msMetrics {
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms)) ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms))
} }
} }
@ -282,3 +364,33 @@ type memStatsMetrics []struct {
eval func(*runtime.MemStats) float64 eval func(*runtime.MemStats) float64
valType ValueType valType ValueType
} }
// NewBuildInfoCollector returns a collector collecting a single metric
// "go_build_info" with the constant value 1 and three labels "path", "version",
// and "checksum". Their label values contain the main module path, version, and
// checksum, respectively. The labels will only have meaningful values if the
// binary is built with Go module support and from source code retrieved from
// the source repository (rather than the local file system). This is usually
// accomplished by building from outside of GOPATH, specifying the full address
// of the main package, e.g. "GO111MODULE=on go run
// github.com/prometheus/client_golang/examples/random". If built without Go
// module support, all label values will be "unknown". If built with Go module
// support but using the source code from the local file system, the "path" will
// be set appropriately, but "checksum" will be empty and "version" will be
// "(devel)".
//
// This collector uses only the build information for the main module. See
// https://github.com/povilasv/prommod for an example of a collector for the
// module dependencies.
func NewBuildInfoCollector() Collector {
path, version, sum := readBuildInfo()
c := &selfCollector{MustNewConstMetric(
NewDesc(
"go_build_info",
"Build information about the main Go module.",
nil, Labels{"path": path, "version": version, "checksum": sum},
),
GaugeValue, 1)}
c.init(c.self)
return c
}

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,29 +123,22 @@ 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.
Help string Help string
// ConstLabels are used to attach fixed labels to this // ConstLabels are used to attach fixed labels to this metric. Metrics
// Histogram. Histograms with the same fully-qualified name must have the // with the same fully-qualified name must have the same label names in
// same label names in their ConstLabels. // their ConstLabels.
// //
// Note that in most cases, labels have a value that varies during the // ConstLabels are only used rarely. In particular, do not use them to
// lifetime of a process. Those labels are usually managed with a // attach the same labels to all your metrics. Those use cases are
// HistogramVec. ConstLabels serve only special purposes. One is for the // better covered by target labels set by the scraping Prometheus
// special case where the value of a label does not change during the // server, or by one specific metric (e.g. a build_info or a
// lifetime of a process, e.g. if the revision of the running binary is // machine_role metric). See also
// put into a label. Another, more advanced purpose is if more than one // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
// Collector needs to collect Histograms with the same fully-qualified
// name. In that case, those Summaries must differ in the values of
// their ConstLabels. See the Collector examples.
//
// If the value of a label never changes (not even between binaries),
// that label most likely should not be a label at all (but part of the
// metric name).
ConstLabels Labels ConstLabels Labels
// Buckets defines the buckets into which observations are counted. Each // Buckets defines the buckets into which observations are counted. Each
@ -169,7 +165,7 @@ func NewHistogram(opts HistogramOpts) Histogram {
func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram { func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
if len(desc.variableLabels) != len(labelValues) { if len(desc.variableLabels) != len(labelValues) {
panic(errInconsistentCardinality) panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
} }
for _, n := range desc.variableLabels { for _, n := range desc.variableLabels {
@ -191,6 +187,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
desc: desc, desc: desc,
upperBounds: opts.Buckets, upperBounds: opts.Buckets,
labelPairs: makeLabelPairs(desc, labelValues), labelPairs: makeLabelPairs(desc, labelValues),
counts: [2]*histogramCounts{&histogramCounts{}, &histogramCounts{}},
} }
for i, upperBound := range h.upperBounds { for i, upperBound := range h.upperBounds {
if i < len(h.upperBounds)-1 { if i < len(h.upperBounds)-1 {
@ -207,30 +204,56 @@ 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 buckets
h.counts = make([]uint64, len(h.upperBounds)) // for both counts:
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 {
// countAndHotIdx enables lock-free writes with use of atomic updates.
// The most significant bit is the hot index [0 or 1] of the count field
// below. Observe calls update the hot one. All remaining bits count the
// number of Observe calls. Observe starts by incrementing this counter,
// and finish by incrementing the count field in the respective
// histogramCounts, as a marker for completion.
//
// Calls of the Write method (which are non-mutating reads from the
// perspective of the histogram) swap the hotcold under the writeMtx
// lock. A cooldown is awaited (while locked) by comparing the number of
// observations with the initiation count. Once they match, then the
// last observation on the now cool one has completed. All cool fields must
// be merged into the new hot before releasing writeMtx.
//
// Fields with atomic access first! See alignment constraint:
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
countAndHotIdx uint64
selfCollector selfCollector
// Note that there is no mutex required. desc *Desc
writeMtx sync.Mutex // Only used in the Write method.
desc *Desc // Two counts, one is "hot" for lock-free observations, the other is
// "cold" for writing out a dto.Metric. It has to be an array of
// pointers to guarantee 64bit alignment of the histogramCounts, see
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
counts [2]*histogramCounts
upperBounds []float64 upperBounds []float64
counts []uint64 labelPairs []*dto.LabelPair
labelPairs []*dto.LabelPair
} }
func (h *histogram) Desc() *Desc { func (h *histogram) Desc() *Desc {
@ -248,36 +271,84 @@ 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 so that the counter in the lower
// 63 bits gets incremented. 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, 1)
hotCounts := h.counts[n>>63]
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{} // For simplicity, we protect this whole method by a mutex. It is not in
buckets := make([]*dto.Bucket, len(h.upperBounds)) // the hot path, i.e. Observe is called much more often than Write. The
// complication of making Write lock-free isn't worth it, if possible at
// all.
h.writeMtx.Lock()
defer h.writeMtx.Unlock()
his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&h.sumBits))) // Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
his.SampleCount = proto.Uint64(atomic.LoadUint64(&h.count)) // without touching the count bits. See the struct comments for a full
var count uint64 // description of the algorithm.
n := atomic.AddUint64(&h.countAndHotIdx, 1<<63)
// count is contained unchanged in the lower 63 bits.
count := n & ((1 << 63) - 1)
// The most significant bit tells us which counts is hot. The complement
// is thus the cold one.
hotCounts := h.counts[n>>63]
coldCounts := h.counts[(^n)>>63]
// Await cooldown.
for count != atomic.LoadUint64(&coldCounts.count) {
runtime.Gosched() // Let observations get work done.
}
his := &dto.Histogram{
Bucket: make([]*dto.Bucket, len(h.upperBounds)),
SampleCount: proto.Uint64(count),
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{ his.Bucket[i] = &dto.Bucket{
CumulativeCount: proto.Uint64(count), CumulativeCount: proto.Uint64(cumCount),
UpperBound: proto.Float64(upperBound), UpperBound: proto.Float64(upperBound),
} }
} }
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
} }
@ -322,7 +393,7 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
// example. // example.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of VariableLabels in Desc. // number of VariableLabels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
@ -330,8 +401,8 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
// latter has a much more readable (albeit more verbose) syntax, but it comes // latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the GaugeVec example. // See also the GaugeVec example.
func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) { func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
metric, err := m.metricVec.getMetricWithLabelValues(lvs...) metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
@ -345,13 +416,13 @@ func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error)
// are the same as for GetMetricWithLabelValues. // are the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc. // with those of the VariableLabels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (m *HistogramVec) GetMetricWith(labels Labels) (Observer, error) { func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
metric, err := m.metricVec.getMetricWith(labels) metric, err := v.metricVec.getMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
@ -359,18 +430,57 @@ func (m *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
} }
// WithLabelValues works as GetMetricWithLabelValues, but panics where // WithLabelValues works as GetMetricWithLabelValues, but panics where
// GetMetricWithLabelValues would have returned an error. By not returning an // GetMetricWithLabelValues would have returned an error. Not returning an
// error, WithLabelValues allows shortcuts like // error allows shortcuts like
// myVec.WithLabelValues("404", "GET").Observe(42.21) // myVec.WithLabelValues("404", "GET").Observe(42.21)
func (m *HistogramVec) WithLabelValues(lvs ...string) Observer { func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
return m.metricVec.withLabelValues(lvs...).(Observer) h, err := v.GetMetricWithLabelValues(lvs...)
if err != nil {
panic(err)
}
return h
} }
// With works as GetMetricWith, but panics where GetMetricWithLabels would have // With works as GetMetricWith but panics where GetMetricWithLabels would have
// returned an error. By not returning an error, With allows shortcuts like // returned an error. Not returning an error allows shortcuts like
// myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21) // myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
func (m *HistogramVec) With(labels Labels) Observer { func (v *HistogramVec) With(labels Labels) Observer {
return m.metricVec.with(labels).(Observer) h, err := v.GetMetricWith(labels)
if err != nil {
panic(err)
}
return h
}
// CurryWith returns a vector curried with the provided labels, i.e. the
// returned vector has those labels pre-set for all labeled operations performed
// on it. The cardinality of the curried vector is reduced accordingly. The
// order of the remaining labels stays the same (just with the curried labels
// taken out of the sequence which is relevant for the
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
// vector, but only with labels not yet used for currying before.
//
// The metrics contained in the HistogramVec are shared between the curried and
// uncurried vectors. They are just accessed differently. Curried and uncurried
// vectors behave identically in terms of collection. Only one must be
// registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector.
func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
vec, err := v.curryWith(labels)
if vec != nil {
return &HistogramVec{vec}, err
}
return nil, err
}
// MustCurryWith works as CurryWith but panics where CurryWith would have
// returned an error.
func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec {
vec, err := v.CurryWith(labels)
if err != nil {
panic(err)
}
return vec
} }
type constHistogram struct { type constHistogram struct {
@ -422,7 +532,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,
@ -430,6 +540,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

@ -15,9 +15,7 @@ package prometheus
import ( import (
"bufio" "bufio"
"bytes"
"compress/gzip" "compress/gzip"
"fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
@ -36,24 +34,14 @@ import (
const ( const (
contentTypeHeader = "Content-Type" contentTypeHeader = "Content-Type"
contentLengthHeader = "Content-Length"
contentEncodingHeader = "Content-Encoding" contentEncodingHeader = "Content-Encoding"
acceptEncodingHeader = "Accept-Encoding" acceptEncodingHeader = "Accept-Encoding"
) )
var bufPool sync.Pool var gzipPool = sync.Pool{
New: func() interface{} {
func getBuf() *bytes.Buffer { return gzip.NewWriter(nil)
buf := bufPool.Get() },
if buf == nil {
return &bytes.Buffer{}
}
return buf.(*bytes.Buffer)
}
func giveBuf(buf *bytes.Buffer) {
buf.Reset()
bufPool.Put(buf)
} }
// Handler returns an HTTP handler for the DefaultGatherer. It is // Handler returns an HTTP handler for the DefaultGatherer. It is
@ -61,69 +49,50 @@ 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 promhttp.Handler instead // InstrumentHandler. You might want to consider using promhttp.Handler instead.
// (which is not instrumented, but can be instrumented with the tooling provided
// in package promhttp).
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(rsp http.ResponseWriter, req *http.Request) {
mfs, err := DefaultGatherer.Gather() mfs, err := DefaultGatherer.Gather()
if err != nil { if err != nil {
http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError) httpError(rsp, err)
return return
} }
contentType := expfmt.Negotiate(req.Header) contentType := expfmt.Negotiate(req.Header)
buf := getBuf() header := rsp.Header()
defer giveBuf(buf) header.Set(contentTypeHeader, string(contentType))
writer, encoding := decorateWriter(req, buf)
enc := expfmt.NewEncoder(writer, contentType) w := io.Writer(rsp)
var lastErr error if gzipAccepted(req.Header) {
header.Set(contentEncodingHeader, "gzip")
gz := gzipPool.Get().(*gzip.Writer)
defer gzipPool.Put(gz)
gz.Reset(w)
defer gz.Close()
w = gz
}
enc := expfmt.NewEncoder(w, contentType)
for _, mf := range mfs { for _, mf := range mfs {
if err := enc.Encode(mf); err != nil { if err := enc.Encode(mf); err != nil {
lastErr = err httpError(rsp, err)
http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
return return
} }
} }
if closer, ok := writer.(io.Closer); ok {
closer.Close()
}
if lastErr != nil && buf.Len() == 0 {
http.Error(w, "No metrics encoded, last error:\n\n"+lastErr.Error(), http.StatusInternalServerError)
return
}
header := w.Header()
header.Set(contentTypeHeader, string(contentType))
header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
if encoding != "" {
header.Set(contentEncodingHeader, encoding)
}
w.Write(buf.Bytes())
}) })
} }
// decorateWriter wraps a writer to handle gzip compression if requested. It
// returns the decorated writer and the appropriate "Content-Encoding" header
// (which is empty if no compression is enabled).
func decorateWriter(request *http.Request, writer io.Writer) (io.Writer, string) {
header := request.Header.Get(acceptEncodingHeader)
parts := strings.Split(header, ",")
for _, part := range parts {
part := strings.TrimSpace(part)
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
return gzip.NewWriter(writer), "gzip"
}
}
return writer, ""
}
var instLabels = []string{"method", "code"} var instLabels = []string{"method", "code"}
type nower interface { type nower interface {
@ -140,16 +109,6 @@ var now nower = nowFunc(func() time.Time {
return time.Now() return time.Now()
}) })
func nowSeries(t ...time.Time) nower {
return nowFunc(func() time.Time {
defer func() {
t = t[1:]
}()
return t[0]
})
}
// InstrumentHandler wraps the given HTTP handler for instrumentation. It // InstrumentHandler wraps the given HTTP handler for instrumentation. It
// registers four metric collectors (if not already done) and reports HTTP // registers four metric collectors (if not already done) and reports HTTP
// metrics to the (newly or already) registered collectors: http_requests_total // metrics to the (newly or already) registered collectors: http_requests_total
@ -160,21 +119,14 @@ func nowSeries(t ...time.Time) nower {
// (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)
@ -318,7 +270,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 {
@ -353,10 +305,9 @@ func computeApproximateRequestSize(r *http.Request) <-chan int {
type responseWriterDelegator struct { type responseWriterDelegator struct {
http.ResponseWriter http.ResponseWriter
handler, method string status int
status int written int64
written int64 wroteHeader bool
wroteHeader bool
} }
func (r *responseWriterDelegator) WriteHeader(code int) { func (r *responseWriterDelegator) WriteHeader(code int) {
@ -379,6 +330,8 @@ type fancyResponseWriterDelegator struct {
} }
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool { func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
//remove support from client_golang yet.
return f.ResponseWriter.(http.CloseNotifier).CloseNotify() return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
} }
@ -522,3 +475,31 @@ func sanitizeCode(s int) string {
return strconv.Itoa(s) return strconv.Itoa(s)
} }
} }
// gzipAccepted returns whether the client will accept gzip-encoded content.
func gzipAccepted(header http.Header) bool {
a := header.Get(acceptEncodingHeader)
parts := strings.Split(a, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
return true
}
}
return false
}
// httpError removes any content-encoding header and then calls http.Error with
// the provided error and http.StatusInternalServerErrer. Error contents is
// supposed to be uncompressed plain text. However, same as with a plain
// http.Error, any header settings will be void if the header has already been
// sent. The error message will still be written to the writer, but it will
// probably be of limited use.
func httpError(rsp http.ResponseWriter, err error) {
rsp.Header().Del(contentEncodingHeader)
http.Error(
rsp,
"An error has occurred while serving metrics:\n\n"+err.Error(),
http.StatusInternalServerError,
)
}

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 (
@ -24,9 +37,22 @@ const reservedLabelPrefix = "__"
var errInconsistentCardinality = errors.New("inconsistent label cardinality") var errInconsistentCardinality = errors.New("inconsistent label cardinality")
func makeInconsistentCardinalityError(fqName string, labels, labelValues []string) error {
return fmt.Errorf(
"%s: %q has %d variable labels named %q but %d values %q were provided",
errInconsistentCardinality, fqName,
len(labels), labels,
len(labelValues), labelValues,
)
}
func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error { func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
if len(labels) != expectedNumberOfValues { if len(labels) != expectedNumberOfValues {
return errInconsistentCardinality return fmt.Errorf(
"%s: expected %d label values but got %d in %#v",
errInconsistentCardinality, expectedNumberOfValues,
len(labels), labels,
)
} }
for name, val := range labels { for name, val := range labels {
@ -40,7 +66,11 @@ func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
func validateLabelValues(vals []string, expectedNumberOfValues int) error { func validateLabelValues(vals []string, expectedNumberOfValues int) error {
if len(vals) != expectedNumberOfValues { if len(vals) != expectedNumberOfValues {
return errInconsistentCardinality return fmt.Errorf(
"%s: expected %d label values but got %d in %#v",
errInconsistentCardinality, expectedNumberOfValues,
len(vals), vals,
)
} }
for _, val := range vals { for _, val := range vals {

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.
@ -79,20 +82,12 @@ type Opts 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.
// //
// Note that in most cases, labels have a value that varies during the // ConstLabels are only used rarely. In particular, do not use them to
// lifetime of a process. Those labels are usually managed with a metric // attach the same labels to all your metrics. Those use cases are
// vector collector (like CounterVec, GaugeVec, UntypedVec). ConstLabels // better covered by target labels set by the scraping Prometheus
// serve only special purposes. One is for the special case where the // server, or by one specific metric (e.g. a build_info or a
// value of a label does not change during the lifetime of a process, // machine_role metric). See also
// e.g. if the revision of the running binary is put into a // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
// label. Another, more advanced purpose is if more than one Collector
// needs to collect Metrics with the same fully-qualified name. In that
// case, those Metrics must differ in the values of their
// ConstLabels. See the Collector examples.
//
// If the value of a label never changes (not even between binaries),
// that label most likely should not be a label at all (but part of the
// metric name).
ConstLabels Labels ConstLabels Labels
} }
@ -118,37 +113,22 @@ 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()
} }
type hashSorter []uint64
func (s hashSorter) Len() int {
return len(s)
}
func (s hashSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s hashSorter) Less(i, j int) bool {
return s[i] < s[j]
}
type invalidMetric struct { type invalidMetric struct {
desc *Desc desc *Desc
err error err error
@ -164,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

@ -45,6 +45,8 @@ type ObserverVec interface {
GetMetricWithLabelValues(lvs ...string) (Observer, error) GetMetricWithLabelValues(lvs ...string) (Observer, error)
With(Labels) Observer With(Labels) Observer
WithLabelValues(...string) Observer WithLabelValues(...string) Observer
CurryWith(Labels) (ObserverVec, error)
MustCurryWith(Labels) ObserverVec
Collector Collector
} }

View file

@ -13,46 +13,74 @@
package prometheus package prometheus
import "github.com/prometheus/procfs" import (
"errors"
"os"
"github.com/prometheus/procfs"
)
type processCollector struct { type processCollector struct {
pid int
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
} }
// NewProcessCollector returns a collector which exports the current state of // ProcessCollectorOpts defines the behavior of a process metrics collector
// process metrics including cpu, memory and file descriptor usage as well as // created with NewProcessCollector.
// the process start time for the given process id under the given namespace. type ProcessCollectorOpts struct {
func NewProcessCollector(pid int, namespace string) Collector { // PidFn returns the PID of the process the collector collects metrics
return NewProcessCollectorPIDFn( // for. It is called upon each collection. By default, the PID of the
func() (int, error) { return pid, nil }, // current process is used, as determined on construction time by
namespace, // 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
} }
// NewProcessCollectorPIDFn returns a collector which exports the current state // NewProcessCollector returns a collector which exports the current state of
// of process metrics including cpu, memory and file descriptor usage as well // process metrics including CPU, memory and file descriptor usage as well as
// as the process start time under the given namespace. The given pidFn is // the process start time. The detailed behavior is defined by the provided
// called on each collect and is used to determine the process to export // ProcessCollectorOpts. The zero value of ProcessCollectorOpts creates a
// metrics for. // collector for the current process with an empty namespace string and no error
func NewProcessCollectorPIDFn( // reporting.
pidFn func() (int, error), //
namespace string, // Currently, the collector depends on a Linux-style proc filesystem and
) Collector { // therefore only exports metrics for Linux.
//
// Note: An older version of this function had the following signature:
//
// NewProcessCollector(pid int, namespace string) Collector
//
// Most commonly, it was called as
//
// NewProcessCollector(os.Getpid(), "")
//
// The following call of the current version is equivalent to the above:
//
// NewProcessCollector(ProcessCollectorOpts{})
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.",
@ -73,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.",
@ -85,12 +118,23 @@ func NewProcessCollectorPIDFn(
), ),
} }
// Set up process metric collection if supported by the runtime. if opts.PidFn == nil {
if _, err := procfs.NewStat(); err == nil { pid := os.Getpid()
c.collectFn = c.processCollect c.pidFn = func() (int, error) { return pid, nil }
} else {
c.pidFn = opts.PidFn
} }
return &c // Set up process metric collection if supported by the runtime.
if _, err := procfs.NewDefaultFS(); err == nil {
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
} }
// Describe returns all descriptions of the collector. // Describe returns all descriptions of the collector.
@ -99,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
} }
@ -108,33 +153,52 @@ 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
} }
if stat, err := p.NewStat(); err == nil { if stat, err := p.Stat(); err == nil {
ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime()) ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime())
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory())) ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory()))
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.Limits(); 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

@ -38,7 +38,6 @@ type delegator interface {
type responseWriterDelegator struct { type responseWriterDelegator struct {
http.ResponseWriter http.ResponseWriter
handler, method string
status int status int
written int64 written int64
wroteHeader bool wroteHeader bool
@ -75,17 +74,20 @@ type closeNotifierDelegator struct{ *responseWriterDelegator }
type flusherDelegator struct{ *responseWriterDelegator } type flusherDelegator struct{ *responseWriterDelegator }
type hijackerDelegator struct{ *responseWriterDelegator } type hijackerDelegator struct{ *responseWriterDelegator }
type readerFromDelegator struct{ *responseWriterDelegator } type readerFromDelegator struct{ *responseWriterDelegator }
type pusherDelegator struct{ *responseWriterDelegator }
func (d *closeNotifierDelegator) CloseNotify() <-chan bool { func (d closeNotifierDelegator) CloseNotify() <-chan bool {
//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
//remove support from client_golang yet.
return d.ResponseWriter.(http.CloseNotifier).CloseNotify() return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
} }
func (d *flusherDelegator) Flush() { func (d flusherDelegator) Flush() {
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)
} }
@ -93,6 +95,9 @@ func (d *readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
d.written += n d.written += n
return n, err return n, err
} }
func (d pusherDelegator) Push(target string, opts *http.PushOptions) error {
return d.ResponseWriter.(http.Pusher).Push(target, opts)
}
var pickDelegator = make([]func(*responseWriterDelegator) delegator, 32) var pickDelegator = make([]func(*responseWriterDelegator) delegator, 32)
@ -112,7 +117,7 @@ func init() {
*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}
@ -122,14 +127,14 @@ func init() {
*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 +142,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 +152,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 +167,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 +182,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 +190,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 +199,159 @@ 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}}
}
pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16
return pusherDelegator{d}
}
pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17
return struct {
*responseWriterDelegator
http.Pusher
http.CloseNotifier
}{d, pusherDelegator{d}, closeNotifierDelegator{d}}
}
pickDelegator[pusher+flusher] = func(d *responseWriterDelegator) delegator { // 18
return struct {
*responseWriterDelegator
http.Pusher
http.Flusher
}{d, pusherDelegator{d}, flusherDelegator{d}}
}
pickDelegator[pusher+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 19
return struct {
*responseWriterDelegator
http.Pusher
http.Flusher
http.CloseNotifier
}{d, pusherDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
}
pickDelegator[pusher+hijacker] = func(d *responseWriterDelegator) delegator { // 20
return struct {
*responseWriterDelegator
http.Pusher
http.Hijacker
}{d, pusherDelegator{d}, hijackerDelegator{d}}
}
pickDelegator[pusher+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 21
return struct {
*responseWriterDelegator
http.Pusher
http.Hijacker
http.CloseNotifier
}{d, pusherDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
}
pickDelegator[pusher+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 22
return struct {
*responseWriterDelegator
http.Pusher
http.Hijacker
http.Flusher
}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
}
pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23
return struct {
*responseWriterDelegator
http.Pusher
http.Hijacker
http.Flusher
http.CloseNotifier
}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
}
pickDelegator[pusher+readerFrom] = func(d *responseWriterDelegator) delegator { // 24
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
}{d, pusherDelegator{d}, readerFromDelegator{d}}
}
pickDelegator[pusher+readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 25
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.CloseNotifier
}{d, pusherDelegator{d}, readerFromDelegator{d}, closeNotifierDelegator{d}}
}
pickDelegator[pusher+readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 26
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Flusher
}{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}}
}
pickDelegator[pusher+readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 27
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Flusher
http.CloseNotifier
}{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
}
pickDelegator[pusher+readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 28
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Hijacker
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}}
}
pickDelegator[pusher+readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 29
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Hijacker
http.CloseNotifier
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
}
pickDelegator[pusher+readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 30
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Hijacker
http.Flusher
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
}
pickDelegator[pusher+readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 31
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Hijacker
http.Flusher
http.CloseNotifier
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
} }
} }
func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
d := &responseWriterDelegator{
ResponseWriter: w,
observeWriteHeader: observeWriteHeaderFunc,
}
id := 0
//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
//remove support from client_golang yet.
if _, ok := w.(http.CloseNotifier); ok {
id += closeNotifier
}
if _, ok := w.(http.Flusher); ok {
id += flusher
}
if _, ok := w.(http.Hijacker); ok {
id += hijacker
}
if _, ok := w.(io.ReaderFrom); ok {
id += readerFrom
}
if _, ok := w.(http.Pusher); ok {
id += pusher
}
return pickDelegator[id](d)
}

View file

@ -1,181 +0,0 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.8
package promhttp
import (
"io"
"net/http"
)
type pusherDelegator struct{ *responseWriterDelegator }
func (d *pusherDelegator) Push(target string, opts *http.PushOptions) error {
return d.ResponseWriter.(http.Pusher).Push(target, opts)
}
func init() {
pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16
return pusherDelegator{d}
}
pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17
return struct {
*responseWriterDelegator
http.Pusher
http.CloseNotifier
}{d, &pusherDelegator{d}, &closeNotifierDelegator{d}}
}
pickDelegator[pusher+flusher] = func(d *responseWriterDelegator) delegator { // 18
return struct {
*responseWriterDelegator
http.Pusher
http.Flusher
}{d, &pusherDelegator{d}, &flusherDelegator{d}}
}
pickDelegator[pusher+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 19
return struct {
*responseWriterDelegator
http.Pusher
http.Flusher
http.CloseNotifier
}{d, &pusherDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
}
pickDelegator[pusher+hijacker] = func(d *responseWriterDelegator) delegator { // 20
return struct {
*responseWriterDelegator
http.Pusher
http.Hijacker
}{d, &pusherDelegator{d}, &hijackerDelegator{d}}
}
pickDelegator[pusher+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 21
return struct {
*responseWriterDelegator
http.Pusher
http.Hijacker
http.CloseNotifier
}{d, &pusherDelegator{d}, &hijackerDelegator{d}, &closeNotifierDelegator{d}}
}
pickDelegator[pusher+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 22
return struct {
*responseWriterDelegator
http.Pusher
http.Hijacker
http.Flusher
}{d, &pusherDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}}
}
pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23
return struct {
*responseWriterDelegator
http.Pusher
http.Hijacker
http.Flusher
http.CloseNotifier
}{d, &pusherDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
}
pickDelegator[pusher+readerFrom] = func(d *responseWriterDelegator) delegator { // 24
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
}{d, &pusherDelegator{d}, &readerFromDelegator{d}}
}
pickDelegator[pusher+readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 25
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.CloseNotifier
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &closeNotifierDelegator{d}}
}
pickDelegator[pusher+readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 26
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Flusher
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &flusherDelegator{d}}
}
pickDelegator[pusher+readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 27
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Flusher
http.CloseNotifier
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
}
pickDelegator[pusher+readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 28
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Hijacker
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}}
}
pickDelegator[pusher+readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 29
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Hijacker
http.CloseNotifier
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}, &closeNotifierDelegator{d}}
}
pickDelegator[pusher+readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 30
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Hijacker
http.Flusher
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}}
}
pickDelegator[pusher+readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 31
return struct {
*responseWriterDelegator
http.Pusher
io.ReaderFrom
http.Hijacker
http.Flusher
http.CloseNotifier
}{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
}
}
func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
d := &responseWriterDelegator{
ResponseWriter: w,
observeWriteHeader: observeWriteHeaderFunc,
}
id := 0
if _, ok := w.(http.CloseNotifier); ok {
id += closeNotifier
}
if _, ok := w.(http.Flusher); ok {
id += flusher
}
if _, ok := w.(http.Hijacker); ok {
id += hijacker
}
if _, ok := w.(io.ReaderFrom); ok {
id += readerFrom
}
if _, ok := w.(http.Pusher); ok {
id += pusher
}
return pickDelegator[id](d)
}

View file

@ -1,44 +0,0 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !go1.8
package promhttp
import (
"io"
"net/http"
)
func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
d := &responseWriterDelegator{
ResponseWriter: w,
observeWriteHeader: observeWriteHeaderFunc,
}
id := 0
if _, ok := w.(http.CloseNotifier); ok {
id += closeNotifier
}
if _, ok := w.(http.Flusher); ok {
id += flusher
}
if _, ok := w.(http.Hijacker); ok {
id += hijacker
}
if _, ok := w.(io.ReaderFrom); ok {
id += readerFrom
}
return pickDelegator[id](d)
}

View file

@ -32,13 +32,13 @@
package promhttp package promhttp
import ( import (
"bytes"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
"time"
"github.com/prometheus/common/expfmt" "github.com/prometheus/common/expfmt"
@ -47,99 +47,204 @@ import (
const ( const (
contentTypeHeader = "Content-Type" contentTypeHeader = "Content-Type"
contentLengthHeader = "Content-Length"
contentEncodingHeader = "Content-Encoding" contentEncodingHeader = "Content-Encoding"
acceptEncodingHeader = "Accept-Encoding" acceptEncodingHeader = "Accept-Encoding"
) )
var bufPool sync.Pool var gzipPool = sync.Pool{
New: func() interface{} {
func getBuf() *bytes.Buffer { return gzip.NewWriter(nil)
buf := bufPool.Get() },
if buf == nil {
return &bytes.Buffer{}
}
return buf.(*bytes.Buffer)
} }
func giveBuf(buf *bytes.Buffer) { // Handler returns an http.Handler for the prometheus.DefaultGatherer, using
buf.Reset() // default HandlerOpts, i.e. it reports the first error as an HTTP error, it has
bufPool.Put(buf) // no error logging, and it applies compression if requested by the client.
}
// Handler returns an HTTP handler for the prometheus.DefaultGatherer. The
// Handler uses the default HandlerOpts, i.e. report the first error as an HTTP
// error, no error logging, and compression if requested by the client.
// //
// If you want to create a Handler for the DefaultGatherer with different // The returned http.Handler is already instrumented using the
// HandlerOpts, create it with HandlerFor with prometheus.DefaultGatherer and // InstrumentMetricHandler function and the prometheus.DefaultRegisterer. If you
// your desired HandlerOpts. // create multiple http.Handlers by separate calls of the Handler function, the
// metrics used for instrumentation will be shared between them, providing
// global scrape counts.
//
// This function is meant to cover the bulk of basic use cases. If you are doing
// anything that requires more customization (including using a non-default
// Gatherer, different instrumentation, and non-default HandlerOpts), use the
// HandlerFor function. See there for details.
func Handler() http.Handler { func Handler() http.Handler {
return HandlerFor(prometheus.DefaultGatherer, HandlerOpts{}) return InstrumentMetricHandler(
prometheus.DefaultRegisterer, HandlerFor(prometheus.DefaultGatherer, HandlerOpts{}),
)
} }
// HandlerFor returns an http.Handler for the provided Gatherer. The behavior // HandlerFor returns an uninstrumented http.Handler for the provided
// of the Handler is defined by the provided HandlerOpts. // Gatherer. The behavior of the Handler is defined by the provided
// HandlerOpts. Thus, HandlerFor is useful to create http.Handlers for custom
// Gatherers, with non-default HandlerOpts, and/or with custom (or no)
// instrumentation. Use the InstrumentMetricHandler function to apply the same
// kind of instrumentation as it is used by the Handler function.
func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { var (
inFlightSem chan struct{}
errCnt = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "promhttp_metric_handler_errors_total",
Help: "Total number of internal errors encountered by the promhttp metric handler.",
},
[]string{"cause"},
)
)
if opts.MaxRequestsInFlight > 0 {
inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight)
}
if opts.Registry != nil {
// Initialize all possibilites that can occur below.
errCnt.WithLabelValues("gathering")
errCnt.WithLabelValues("encoding")
if err := opts.Registry.Register(errCnt); err != nil {
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
errCnt = are.ExistingCollector.(*prometheus.CounterVec)
} else {
panic(err)
}
}
}
h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
if inFlightSem != nil {
select {
case inFlightSem <- struct{}{}: // All good, carry on.
defer func() { <-inFlightSem }()
default:
http.Error(rsp, fmt.Sprintf(
"Limit of concurrent requests reached (%d), try again later.", opts.MaxRequestsInFlight,
), http.StatusServiceUnavailable)
return
}
}
mfs, err := reg.Gather() mfs, err := reg.Gather()
if err != nil { if err != nil {
if opts.ErrorLog != nil { if opts.ErrorLog != nil {
opts.ErrorLog.Println("error gathering metrics:", err) opts.ErrorLog.Println("error gathering metrics:", err)
} }
errCnt.WithLabelValues("gathering").Inc()
switch opts.ErrorHandling { switch opts.ErrorHandling {
case PanicOnError: case PanicOnError:
panic(err) panic(err)
case ContinueOnError: case ContinueOnError:
if len(mfs) == 0 { if len(mfs) == 0 {
http.Error(w, "No metrics gathered, last error:\n\n"+err.Error(), http.StatusInternalServerError) // Still report the error if no metrics have been gathered.
httpError(rsp, err)
return return
} }
case HTTPErrorOnError: case HTTPErrorOnError:
http.Error(w, "An error has occurred during metrics gathering:\n\n"+err.Error(), http.StatusInternalServerError) httpError(rsp, err)
return return
} }
} }
contentType := expfmt.Negotiate(req.Header) contentType := expfmt.Negotiate(req.Header)
buf := getBuf() header := rsp.Header()
defer giveBuf(buf) header.Set(contentTypeHeader, string(contentType))
writer, encoding := decorateWriter(req, buf, opts.DisableCompression)
enc := expfmt.NewEncoder(writer, contentType) w := io.Writer(rsp)
if !opts.DisableCompression && gzipAccepted(req.Header) {
header.Set(contentEncodingHeader, "gzip")
gz := gzipPool.Get().(*gzip.Writer)
defer gzipPool.Put(gz)
gz.Reset(w)
defer gz.Close()
w = gz
}
enc := expfmt.NewEncoder(w, contentType)
var lastErr error var lastErr error
for _, mf := range mfs { for _, mf := range mfs {
if err := enc.Encode(mf); err != nil { if err := enc.Encode(mf); err != nil {
lastErr = err lastErr = err
if opts.ErrorLog != nil { if opts.ErrorLog != nil {
opts.ErrorLog.Println("error encoding metric family:", err) opts.ErrorLog.Println("error encoding and sending metric family:", err)
} }
errCnt.WithLabelValues("encoding").Inc()
switch opts.ErrorHandling { switch opts.ErrorHandling {
case PanicOnError: case PanicOnError:
panic(err) panic(err)
case ContinueOnError: case ContinueOnError:
// Handled later. // Handled later.
case HTTPErrorOnError: case HTTPErrorOnError:
http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError) httpError(rsp, err)
return return
} }
} }
} }
if closer, ok := writer.(io.Closer); ok {
closer.Close() if lastErr != nil {
httpError(rsp, lastErr)
} }
if lastErr != nil && buf.Len() == 0 {
http.Error(w, "No metrics encoded, last error:\n\n"+lastErr.Error(), http.StatusInternalServerError)
return
}
header := w.Header()
header.Set(contentTypeHeader, string(contentType))
header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
if encoding != "" {
header.Set(contentEncodingHeader, encoding)
}
w.Write(buf.Bytes())
// TODO(beorn7): Consider streaming serving of metrics.
}) })
if opts.Timeout <= 0 {
return h
}
return http.TimeoutHandler(h, opts.Timeout, fmt.Sprintf(
"Exceeded configured timeout of %v.\n",
opts.Timeout,
))
}
// InstrumentMetricHandler is usually used with an http.Handler returned by the
// HandlerFor function. It instruments the provided http.Handler with two
// metrics: A counter vector "promhttp_metric_handler_requests_total" to count
// scrapes partitioned by HTTP status code, and a gauge
// "promhttp_metric_handler_requests_in_flight" to track the number of
// simultaneous scrapes. This function idempotently registers collectors for
// both metrics with the provided Registerer. It panics if the registration
// fails. The provided metrics are useful to see how many scrapes hit the
// monitored target (which could be from different Prometheus servers or other
// scrapers), and how often they overlap (which would result in more than one
// scrape in flight at the same time). Note that the scrapes-in-flight gauge
// will contain the scrape by which it is exposed, while the scrape counter will
// only get incremented after the scrape is complete (as only then the status
// code is known). For tracking scrape durations, use the
// "scrape_duration_seconds" gauge created by the Prometheus server upon each
// scrape.
func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) http.Handler {
cnt := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "promhttp_metric_handler_requests_total",
Help: "Total number of scrapes by HTTP status code.",
},
[]string{"code"},
)
// Initialize the most likely HTTP status codes.
cnt.WithLabelValues("200")
cnt.WithLabelValues("500")
cnt.WithLabelValues("503")
if err := reg.Register(cnt); err != nil {
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
cnt = are.ExistingCollector.(*prometheus.CounterVec)
} else {
panic(err)
}
}
gge := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "promhttp_metric_handler_requests_in_flight",
Help: "Current number of scrapes being served.",
})
if err := reg.Register(gge); err != nil {
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
gge = are.ExistingCollector.(prometheus.Gauge)
} else {
panic(err)
}
}
return InstrumentHandlerCounter(cnt, InstrumentHandlerInFlight(gge, handler))
} }
// HandlerErrorHandling defines how a Handler serving metrics will handle // HandlerErrorHandling defines how a Handler serving metrics will handle
@ -155,9 +260,12 @@ const (
// Ignore errors and try to serve as many metrics as possible. However, // Ignore errors and try to serve as many metrics as possible. However,
// if no metrics can be served, serve an HTTP status code 500 and the // if no metrics can be served, serve an HTTP status code 500 and the
// last error message in the body. Only use this in deliberate "best // last error message in the body. Only use this in deliberate "best
// effort" metrics collection scenarios. It is recommended to at least // effort" metrics collection scenarios. In this case, it is highly
// log errors (by providing an ErrorLog in HandlerOpts) to not mask // recommended to provide other means of detecting errors: By setting an
// errors completely. // ErrorLog in HandlerOpts, the errors are logged. By providing a
// Registry in HandlerOpts, the exposed metrics include an error counter
// "promhttp_metric_handler_errors_total", which can be used for
// alerts.
ContinueOnError ContinueOnError
// Panic upon the first error encountered (useful for "crash only" apps). // Panic upon the first error encountered (useful for "crash only" apps).
PanicOnError PanicOnError
@ -180,25 +288,62 @@ type HandlerOpts struct {
// logged regardless of the configured ErrorHandling provided ErrorLog // logged regardless of the configured ErrorHandling provided ErrorLog
// is not nil. // is not nil.
ErrorHandling HandlerErrorHandling ErrorHandling HandlerErrorHandling
// If Registry is not nil, it is used to register a metric
// "promhttp_metric_handler_errors_total", partitioned by "cause". A
// failed registration causes a panic. Note that this error counter is
// different from the instrumentation you get from the various
// InstrumentHandler... helpers. It counts errors that don't necessarily
// result in a non-2xx HTTP status code. There are two typical cases:
// (1) Encoding errors that only happen after streaming of the HTTP body
// has already started (and the status code 200 has been sent). This
// should only happen with custom collectors. (2) Collection errors with
// no effect on the HTTP status code because ErrorHandling is set to
// ContinueOnError.
Registry prometheus.Registerer
// If DisableCompression is true, the handler will never compress the // If DisableCompression is true, the handler will never compress the
// response, even if requested by the client. // response, even if requested by the client.
DisableCompression bool DisableCompression bool
// The number of concurrent HTTP requests is limited to
// MaxRequestsInFlight. Additional requests are responded to with 503
// Service Unavailable and a suitable message in the body. If
// MaxRequestsInFlight is 0 or negative, no limit is applied.
MaxRequestsInFlight int
// If handling a request takes longer than Timeout, it is responded to
// with 503 ServiceUnavailable and a suitable Message. No timeout is
// applied if Timeout is 0 or negative. Note that with the current
// implementation, reaching the timeout simply ends the HTTP requests as
// described above (and even that only if sending of the body hasn't
// started yet), while the bulk work of gathering all the metrics keeps
// running in the background (with the eventual result to be thrown
// away). Until the implementation is improved, it is recommended to
// implement a separate timeout in potentially slow Collectors.
Timeout time.Duration
} }
// decorateWriter wraps a writer to handle gzip compression if requested. It // gzipAccepted returns whether the client will accept gzip-encoded content.
// returns the decorated writer and the appropriate "Content-Encoding" header func gzipAccepted(header http.Header) bool {
// (which is empty if no compression is enabled). a := header.Get(acceptEncodingHeader)
func decorateWriter(request *http.Request, writer io.Writer, compressionDisabled bool) (io.Writer, string) { parts := strings.Split(a, ",")
if compressionDisabled {
return writer, ""
}
header := request.Header.Get(acceptEncodingHeader)
parts := strings.Split(header, ",")
for _, part := range parts { for _, part := range parts {
part := strings.TrimSpace(part) part = strings.TrimSpace(part)
if part == "gzip" || strings.HasPrefix(part, "gzip;") { if part == "gzip" || strings.HasPrefix(part, "gzip;") {
return gzip.NewWriter(writer), "gzip" return true
} }
} }
return writer, "" return false
}
// httpError removes any content-encoding header and then calls http.Error with
// the provided error and http.StatusInternalServerErrer. Error contents is
// supposed to be uncompressed plain text. However, same as with a plain
// http.Error, any header settings will be void if the header has already been
// sent. The error message will still be written to the writer, but it will
// probably be of limited use.
func httpError(rsp http.ResponseWriter, err error) {
rsp.Header().Del(contentEncodingHeader)
http.Error(
rsp,
"An error has occurred while serving metrics:\n\n"+err.Error(),
http.StatusInternalServerError,
)
} }

View file

@ -14,7 +14,9 @@
package promhttp package promhttp
import ( import (
"crypto/tls"
"net/http" "net/http"
"net/http/httptrace"
"time" "time"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -45,12 +47,11 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp
// InstrumentRoundTripperCounter is a middleware that wraps the provided // InstrumentRoundTripperCounter is a middleware that wraps the provided
// http.RoundTripper to observe the request result with the provided CounterVec. // http.RoundTripper to observe the request result with the provided CounterVec.
// The CounterVec must have zero, one, or two labels. The only allowed label // The CounterVec must have zero, one, or two non-const non-curried labels. For
// names are "code" and "method". The function panics if any other instance // those, the only allowed label names are "code" and "method". The function
// labels are provided. Partitioning of the CounterVec happens by HTTP status // panics otherwise. Partitioning of the CounterVec happens by HTTP status code
// code and/or HTTP method if the respective instance label names are present // and/or HTTP method if the respective instance label names are present in the
// in the CounterVec. For unpartitioned counting, use a CounterVec with // CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
// zero labels.
// //
// If the wrapped RoundTripper panics or returns a non-nil error, the Counter // If the wrapped RoundTripper panics or returns a non-nil error, the Counter
// is not incremented. // is not incremented.
@ -69,15 +70,15 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
} }
// InstrumentRoundTripperDuration is a middleware that wraps the provided // InstrumentRoundTripperDuration is a middleware that wraps the provided
// http.RoundTripper to observe the request duration with the provided ObserverVec. // http.RoundTripper to observe the request duration with the provided
// The ObserverVec must have zero, one, or two labels. The only allowed label // ObserverVec. The ObserverVec must have zero, one, or two non-const
// names are "code" and "method". The function panics if any other instance // non-curried labels. For those, the only allowed label names are "code" and
// labels are provided. The Observe method of the Observer in the ObserverVec // "method". The function panics otherwise. The Observe method of the Observer
// is called with the request duration in seconds. Partitioning happens by HTTP // in the ObserverVec is called with the request duration in
// status code and/or HTTP method if the respective instance label names are // seconds. Partitioning happens by HTTP status code and/or HTTP method if the
// present in the ObserverVec. For unpartitioned observations, use an // respective instance label names are present in the ObserverVec. For
// ObserverVec with zero labels. Note that partitioning of Histograms is // unpartitioned observations, use an ObserverVec with zero labels. Note that
// expensive and should be used judiciously. // partitioning of Histograms is expensive and should be used judiciously.
// //
// If the wrapped RoundTripper panics or returns a non-nil error, no values are // If the wrapped RoundTripper panics or returns a non-nil error, no values are
// reported. // reported.
@ -96,3 +97,123 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT
return resp, err return resp, err
}) })
} }
// InstrumentTrace is used to offer flexibility in instrumenting the available
// httptrace.ClientTrace hook functions. Each function is passed a float64
// representing the time in seconds since the start of the http request. A user
// may choose to use separately buckets Histograms, or implement custom
// instance labels on a per function basis.
type InstrumentTrace struct {
GotConn func(float64)
PutIdleConn func(float64)
GotFirstResponseByte func(float64)
Got100Continue func(float64)
DNSStart func(float64)
DNSDone func(float64)
ConnectStart func(float64)
ConnectDone func(float64)
TLSHandshakeStart func(float64)
TLSHandshakeDone func(float64)
WroteHeaders func(float64)
Wait100Continue func(float64)
WroteRequest func(float64)
}
// InstrumentRoundTripperTrace is a middleware that wraps the provided
// RoundTripper and reports times to hook functions provided in the
// InstrumentTrace struct. Hook functions that are not present in the provided
// InstrumentTrace struct are ignored. Times reported to the hook functions are
// time since the start of the request. Only with Go1.9+, those times are
// guaranteed to never be negative. (Earlier Go versions are not using a
// monotonic clock.) Note that partitioning of Histograms is expensive and
// should be used judiciously.
//
// For hook functions that receive an error as an argument, no observations are
// made in the event of a non-nil error value.
//
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
start := time.Now()
trace := &httptrace.ClientTrace{
GotConn: func(_ httptrace.GotConnInfo) {
if it.GotConn != nil {
it.GotConn(time.Since(start).Seconds())
}
},
PutIdleConn: func(err error) {
if err != nil {
return
}
if it.PutIdleConn != nil {
it.PutIdleConn(time.Since(start).Seconds())
}
},
DNSStart: func(_ httptrace.DNSStartInfo) {
if it.DNSStart != nil {
it.DNSStart(time.Since(start).Seconds())
}
},
DNSDone: func(_ httptrace.DNSDoneInfo) {
if it.DNSDone != nil {
it.DNSDone(time.Since(start).Seconds())
}
},
ConnectStart: func(_, _ string) {
if it.ConnectStart != nil {
it.ConnectStart(time.Since(start).Seconds())
}
},
ConnectDone: func(_, _ string, err error) {
if err != nil {
return
}
if it.ConnectDone != nil {
it.ConnectDone(time.Since(start).Seconds())
}
},
GotFirstResponseByte: func() {
if it.GotFirstResponseByte != nil {
it.GotFirstResponseByte(time.Since(start).Seconds())
}
},
Got100Continue: func() {
if it.Got100Continue != nil {
it.Got100Continue(time.Since(start).Seconds())
}
},
TLSHandshakeStart: func() {
if it.TLSHandshakeStart != nil {
it.TLSHandshakeStart(time.Since(start).Seconds())
}
},
TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
if err != nil {
return
}
if it.TLSHandshakeDone != nil {
it.TLSHandshakeDone(time.Since(start).Seconds())
}
},
WroteHeaders: func() {
if it.WroteHeaders != nil {
it.WroteHeaders(time.Since(start).Seconds())
}
},
Wait100Continue: func() {
if it.Wait100Continue != nil {
it.Wait100Continue(time.Since(start).Seconds())
}
},
WroteRequest: func(_ httptrace.WroteRequestInfo) {
if it.WroteRequest != nil {
it.WroteRequest(time.Since(start).Seconds())
}
},
}
r = r.WithContext(httptrace.WithClientTrace(r.Context(), trace))
return next.RoundTrip(r)
})
}

View file

@ -1,144 +0,0 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.8
package promhttp
import (
"context"
"crypto/tls"
"net/http"
"net/http/httptrace"
"time"
)
// InstrumentTrace is used to offer flexibility in instrumenting the available
// httptrace.ClientTrace hook functions. Each function is passed a float64
// representing the time in seconds since the start of the http request. A user
// may choose to use separately buckets Histograms, or implement custom
// instance labels on a per function basis.
type InstrumentTrace struct {
GotConn func(float64)
PutIdleConn func(float64)
GotFirstResponseByte func(float64)
Got100Continue func(float64)
DNSStart func(float64)
DNSDone func(float64)
ConnectStart func(float64)
ConnectDone func(float64)
TLSHandshakeStart func(float64)
TLSHandshakeDone func(float64)
WroteHeaders func(float64)
Wait100Continue func(float64)
WroteRequest func(float64)
}
// InstrumentRoundTripperTrace is a middleware that wraps the provided
// RoundTripper and reports times to hook functions provided in the
// InstrumentTrace struct. Hook functions that are not present in the provided
// InstrumentTrace struct are ignored. Times reported to the hook functions are
// time since the start of the request. Only with Go1.9+, those times are
// guaranteed to never be negative. (Earlier Go versions are not using a
// monotonic clock.) Note that partitioning of Histograms is expensive and
// should be used judiciously.
//
// For hook functions that receive an error as an argument, no observations are
// made in the event of a non-nil error value.
//
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
start := time.Now()
trace := &httptrace.ClientTrace{
GotConn: func(_ httptrace.GotConnInfo) {
if it.GotConn != nil {
it.GotConn(time.Since(start).Seconds())
}
},
PutIdleConn: func(err error) {
if err != nil {
return
}
if it.PutIdleConn != nil {
it.PutIdleConn(time.Since(start).Seconds())
}
},
DNSStart: func(_ httptrace.DNSStartInfo) {
if it.DNSStart != nil {
it.DNSStart(time.Since(start).Seconds())
}
},
DNSDone: func(_ httptrace.DNSDoneInfo) {
if it.DNSStart != nil {
it.DNSStart(time.Since(start).Seconds())
}
},
ConnectStart: func(_, _ string) {
if it.ConnectStart != nil {
it.ConnectStart(time.Since(start).Seconds())
}
},
ConnectDone: func(_, _ string, err error) {
if err != nil {
return
}
if it.ConnectDone != nil {
it.ConnectDone(time.Since(start).Seconds())
}
},
GotFirstResponseByte: func() {
if it.GotFirstResponseByte != nil {
it.GotFirstResponseByte(time.Since(start).Seconds())
}
},
Got100Continue: func() {
if it.Got100Continue != nil {
it.Got100Continue(time.Since(start).Seconds())
}
},
TLSHandshakeStart: func() {
if it.TLSHandshakeStart != nil {
it.TLSHandshakeStart(time.Since(start).Seconds())
}
},
TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
if err != nil {
return
}
if it.TLSHandshakeDone != nil {
it.TLSHandshakeDone(time.Since(start).Seconds())
}
},
WroteHeaders: func() {
if it.WroteHeaders != nil {
it.WroteHeaders(time.Since(start).Seconds())
}
},
Wait100Continue: func() {
if it.Wait100Continue != nil {
it.Wait100Continue(time.Since(start).Seconds())
}
},
WroteRequest: func(_ httptrace.WroteRequestInfo) {
if it.WroteRequest != nil {
it.WroteRequest(time.Since(start).Seconds())
}
},
}
r = r.WithContext(httptrace.WithClientTrace(context.Background(), trace))
return next.RoundTrip(r)
})
}

View file

@ -14,6 +14,7 @@
package promhttp package promhttp
import ( import (
"errors"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -42,10 +43,10 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl
// InstrumentHandlerDuration is a middleware that wraps the provided // InstrumentHandlerDuration is a middleware that wraps the provided
// http.Handler to observe the request duration with the provided ObserverVec. // http.Handler to observe the request duration with the provided ObserverVec.
// The ObserverVec must have zero, one, or two labels. The only allowed label // The ObserverVec must have zero, one, or two non-const non-curried labels. For
// names are "code" and "method". The function panics if any other instance // those, the only allowed label names are "code" and "method". The function
// labels are provided. The Observe method of the Observer in the ObserverVec // panics otherwise. The Observe method of the Observer in the ObserverVec is
// is called with the request duration in seconds. Partitioning happens by HTTP // called with the request duration in seconds. Partitioning happens by HTTP
// status code and/or HTTP method if the respective instance label names are // status code and/or HTTP method if the respective instance label names are
// present in the ObserverVec. For unpartitioned observations, use an // present in the ObserverVec. For unpartitioned observations, use an
// ObserverVec with zero labels. Note that partitioning of Histograms is // ObserverVec with zero labels. Note that partitioning of Histograms is
@ -77,14 +78,13 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht
}) })
} }
// InstrumentHandlerCounter is a middleware that wraps the provided // InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
// http.Handler to observe the request result with the provided CounterVec. // to observe the request result with the provided CounterVec. The CounterVec
// The CounterVec must have zero, one, or two labels. The only allowed label // must have zero, one, or two non-const non-curried labels. For those, the only
// names are "code" and "method". The function panics if any other instance // allowed label names are "code" and "method". The function panics
// labels are provided. Partitioning of the CounterVec happens by HTTP status // otherwise. Partitioning of the CounterVec happens by HTTP status code and/or
// code and/or HTTP method if the respective instance label names are present // HTTP method if the respective instance label names are present in the
// in the CounterVec. For unpartitioned counting, use a CounterVec with // CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
// zero labels.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
@ -111,14 +111,13 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler)
// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided // InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
// http.Handler to observe with the provided ObserverVec the request duration // http.Handler to observe with the provided ObserverVec the request duration
// until the response headers are written. The ObserverVec must have zero, one, // until the response headers are written. The ObserverVec must have zero, one,
// or two labels. The only allowed label names are "code" and "method". The // or two non-const non-curried labels. For those, the only allowed label names
// function panics if any other instance labels are provided. The Observe // are "code" and "method". The function panics otherwise. The Observe method of
// method of the Observer in the ObserverVec is called with the request // the Observer in the ObserverVec is called with the request duration in
// duration in seconds. Partitioning happens by HTTP status code and/or HTTP // seconds. Partitioning happens by HTTP status code and/or HTTP method if the
// method if the respective instance label names are present in the // respective instance label names are present in the ObserverVec. For
// ObserverVec. For unpartitioned observations, use an ObserverVec with zero // unpartitioned observations, use an ObserverVec with zero labels. Note that
// labels. Note that partitioning of Histograms is expensive and should be used // partitioning of Histograms is expensive and should be used judiciously.
// judiciously.
// //
// If the wrapped Handler panics before calling WriteHeader, no value is // If the wrapped Handler panics before calling WriteHeader, no value is
// reported. // reported.
@ -140,15 +139,15 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
} }
// InstrumentHandlerRequestSize is a middleware that wraps the provided // InstrumentHandlerRequestSize is a middleware that wraps the provided
// http.Handler to observe the request size with the provided ObserverVec. // http.Handler to observe the request size with the provided ObserverVec. The
// The ObserverVec must have zero, one, or two labels. The only allowed label // ObserverVec must have zero, one, or two non-const non-curried labels. For
// names are "code" and "method". The function panics if any other instance // those, the only allowed label names are "code" and "method". The function
// labels are provided. The Observe method of the Observer in the ObserverVec // panics otherwise. The Observe method of the Observer in the ObserverVec is
// is called with the request size in bytes. Partitioning happens by HTTP // called with the request size in bytes. Partitioning happens by HTTP status
// status code and/or HTTP method if the respective instance label names are // code and/or HTTP method if the respective instance label names are present in
// present in the ObserverVec. For unpartitioned observations, use an // the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
// ObserverVec with zero labels. Note that partitioning of Histograms is // labels. Note that partitioning of Histograms is expensive and should be used
// expensive and should be used judiciously. // judiciously.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
@ -175,15 +174,15 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler)
} }
// InstrumentHandlerResponseSize is a middleware that wraps the provided // InstrumentHandlerResponseSize is a middleware that wraps the provided
// http.Handler to observe the response size with the provided ObserverVec. // http.Handler to observe the response size with the provided ObserverVec. The
// The ObserverVec must have zero, one, or two labels. The only allowed label // ObserverVec must have zero, one, or two non-const non-curried labels. For
// names are "code" and "method". The function panics if any other instance // those, the only allowed label names are "code" and "method". The function
// labels are provided. The Observe method of the Observer in the ObserverVec // panics otherwise. The Observe method of the Observer in the ObserverVec is
// is called with the response size in bytes. Partitioning happens by HTTP // called with the response size in bytes. Partitioning happens by HTTP status
// status code and/or HTTP method if the respective instance label names are // code and/or HTTP method if the respective instance label names are present in
// present in the ObserverVec. For unpartitioned observations, use an // the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
// ObserverVec with zero labels. Note that partitioning of Histograms is // labels. Note that partitioning of Histograms is expensive and should be used
// expensive and should be used judiciously. // judiciously.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
@ -204,9 +203,12 @@ func checkLabels(c prometheus.Collector) (code bool, method bool) {
// once Descriptors can have their dimensionality queried. // once Descriptors can have their dimensionality queried.
var ( var (
desc *prometheus.Desc desc *prometheus.Desc
m prometheus.Metric
pm dto.Metric pm dto.Metric
lvs []string
) )
// Get the Desc from the Collector.
descc := make(chan *prometheus.Desc, 1) descc := make(chan *prometheus.Desc, 1)
c.Describe(descc) c.Describe(descc)
@ -223,49 +225,54 @@ func checkLabels(c prometheus.Collector) (code bool, method bool) {
close(descc) close(descc)
if _, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0); err == nil { // Create a ConstMetric with the Desc. Since we don't know how many
return // variable labels there are, try for as long as it needs.
for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) {
m, err = prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, lvs...)
} }
if m, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, magicString); err == nil {
if err := m.Write(&pm); err != nil { // Write out the metric into a proto message and look at the labels.
panic("error checking metric for labels") // If the value is not the magicString, it is a constLabel, which doesn't interest us.
} // If the label is curried, it doesn't interest us.
for _, label := range pm.Label { // In all other cases, only "code" or "method" is allowed.
name, value := label.GetName(), label.GetValue() if err := m.Write(&pm); err != nil {
if value != magicString { panic("error checking metric for labels")
continue
}
switch name {
case "code":
code = true
case "method":
method = true
default:
panic("metric partitioned with non-supported labels")
}
return
}
panic("previously set label not found this must never happen")
} }
if m, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, magicString, magicString); err == nil { for _, label := range pm.Label {
if err := m.Write(&pm); err != nil { name, value := label.GetName(), label.GetValue()
panic("error checking metric for labels") if value != magicString || isLabelCurried(c, name) {
continue
} }
for _, label := range pm.Label { switch name {
name, value := label.GetName(), label.GetValue() case "code":
if value != magicString { code = true
continue case "method":
} method = true
if name == "code" || name == "method" { default:
continue
}
panic("metric partitioned with non-supported labels") panic("metric partitioned with non-supported labels")
} }
code = true
method = true
return
} }
panic("metric partitioned with non-supported labels") return
}
func isLabelCurried(c prometheus.Collector, label string) bool {
// This is even hackier than the label test above.
// We essentially try to curry again and see if it works.
// But for that, we need to type-convert to the two
// types we use here, ObserverVec or *CounterVec.
switch v := c.(type) {
case *prometheus.CounterVec:
if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
return false
}
case prometheus.ObserverVec:
if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
return false
}
default:
panic("unsupported metric vec type")
}
return true
} }
// emptyLabels is a one-time allocation for non-partitioned metrics to avoid // emptyLabels is a one-time allocation for non-partitioned metrics to avoid

View file

@ -15,16 +15,22 @@ package prometheus
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath"
"runtime"
"sort" "sort"
"strings"
"sync" "sync"
"unicode/utf8" "unicode/utf8"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/common/expfmt"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/prometheus/client_golang/prometheus/internal"
) )
const ( const (
@ -36,13 +42,14 @@ const (
// DefaultRegisterer and DefaultGatherer are the implementations of the // DefaultRegisterer and DefaultGatherer are the implementations of the
// Registerer and Gatherer interface a number of convenience functions in this // Registerer and Gatherer interface a number of convenience functions in this
// package act on. Initially, both variables point to the same Registry, which // package act on. Initially, both variables point to the same Registry, which
// has a process collector (see NewProcessCollector) and a Go collector (see // has a process collector (currently on Linux only, see NewProcessCollector)
// NewGoCollector) already registered. This approach to keep default instances // and a Go collector (see NewGoCollector, in particular the note about
// as global state mirrors the approach of other packages in the Go standard // stop-the-world implication with Go versions older than 1.9) already
// library. Note that there are caveats. Change the variables with caution and // registered. This approach to keep default instances as global state mirrors
// only if you understand the consequences. Users who want to avoid global state // the approach of other packages in the Go standard library. Note that there
// altogether should not use the convenience function and act on custom // are caveats. Change the variables with caution and only if you understand the
// instances instead. // consequences. Users who want to avoid global state altogether should not use
// the convenience functions and act on custom instances instead.
var ( var (
defaultRegistry = NewRegistry() defaultRegistry = NewRegistry()
DefaultRegisterer Registerer = defaultRegistry DefaultRegisterer Registerer = defaultRegistry
@ -50,7 +57,7 @@ var (
) )
func init() { func init() {
MustRegister(NewProcessCollector(os.Getpid(), "")) MustRegister(NewProcessCollector(ProcessCollectorOpts{}))
MustRegister(NewGoCollector()) MustRegister(NewGoCollector())
} }
@ -66,7 +73,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
@ -96,8 +104,13 @@ 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.
// //
// It is in general not safe to register the same Collector multiple // A Collector whose Describe method does not yield any Desc is treated
// times concurrently. // 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.)
Register(Collector) error Register(Collector) error
// MustRegister works like Register but registers any number of // MustRegister works like Register but registers any number of
// Collectors and panics upon the first registration that causes an // Collectors and panics upon the first registration that causes an
@ -106,7 +119,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
@ -124,15 +139,23 @@ type Registerer interface {
type Gatherer interface { type Gatherer interface {
// Gather calls the Collect method of the registered Collectors and then // Gather calls the Collect method of the registered Collectors and then
// gathers the collected metrics into a lexicographically sorted slice // gathers the collected metrics into a lexicographically sorted slice
// of MetricFamily protobufs. Even if an error occurs, Gather attempts // of uniquely named MetricFamily protobufs. Gather ensures that the
// to gather as many metrics as possible. Hence, if a non-nil error is // returned slice is valid and self-consistent so that it can be used
// returned, the returned MetricFamily slice could be nil (in case of a // for valid exposition. As an exception to the strict consistency
// fatal error that prevented any meaningful metric collection) or // requirements described for metric.Desc, Gather will tolerate
// contain a number of MetricFamily protobufs, some of which might be // different sets of label names for metrics of the same metric family.
// incomplete, and some might be missing altogether. The returned error //
// (which might be a MultiError) explains the details. In scenarios // Even if an error occurs, Gather attempts to gather as many metrics as
// where complete collection is critical, the returned MetricFamily // possible. Hence, if a non-nil error is returned, the returned
// protobufs should be disregarded if the returned error is non-nil. // MetricFamily slice could be nil (in case of a fatal error that
// prevented any meaningful metric collection) or contain a number of
// MetricFamily protobufs, some of which might be incomplete, and some
// might be missing altogether. The returned error (which might be a
// MultiError) explains the details. Note that this is mostly useful for
// debugging purposes. If the gathered protobufs are to be used for
// exposition in actual monitoring, it is almost always better to not
// expose an incomplete result and instead disregard the returned
// MetricFamily protobufs in case the returned error is non-nil.
Gather() ([]*dto.MetricFamily, error) Gather() ([]*dto.MetricFamily, error)
} }
@ -202,6 +225,13 @@ func (errs MultiError) Error() string {
return buf.String() return buf.String()
} }
// Append appends the provided error if it is not nil.
func (errs *MultiError) Append(err error) {
if err != nil {
*errs = append(*errs, err)
}
}
// MaybeUnwrap returns nil if len(errs) is 0. It returns the first and only // MaybeUnwrap returns nil if len(errs) is 0. It returns the first and only
// contained error as error if len(errs is 1). In all other cases, it returns // contained error as error if len(errs is 1). In all other cases, it returns
// the MultiError directly. This is helpful for returning a MultiError in a way // the MultiError directly. This is helpful for returning a MultiError in a way
@ -226,6 +256,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
} }
@ -243,7 +274,12 @@ func (r *Registry) Register(c Collector) error {
close(descChan) close(descChan)
}() }()
r.mtx.Lock() r.mtx.Lock()
defer r.mtx.Unlock() defer func() {
// Drain channel in case of premature return to not leak a goroutine.
for range descChan {
}
r.mtx.Unlock()
}()
// Conduct various tests... // Conduct various tests...
for desc := range descChan { for desc := range descChan {
@ -283,9 +319,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{
@ -359,31 +396,25 @@ 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)
dimHashes = map[string]uint64{} metricHashes = map[uint64]struct{}{}
wg sync.WaitGroup wg sync.WaitGroup
errs MultiError // The collected errors to return in the end. errs MultiError // The collected errors to return in the end.
registeredDescIDs map[uint64]struct{} // Only used for pedantic checks registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
) )
r.mtx.RLock() r.mtx.RLock()
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))
checkedCollectors := make(chan Collector, len(r.collectorsByID))
// Scatter. uncheckedCollectors := make(chan Collector, len(r.uncheckedCollectors))
// (Collectors could be complex and slow, so we call them all at once.)
wg.Add(len(r.collectorsByID))
go func() {
wg.Wait()
close(metricChan)
}()
for _, collector := range r.collectorsByID { for _, collector := range r.collectorsByID {
go func(collector Collector) { checkedCollectors <- collector
defer wg.Done() }
collector.Collect(metricChan) for _, collector := range r.uncheckedCollectors {
}(collector) 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.
if r.pedanticChecksEnabled { if r.pedanticChecksEnabled {
@ -392,133 +423,264 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
registeredDescIDs[id] = struct{}{} registeredDescIDs[id] = struct{}{}
} }
} }
r.mtx.RUnlock() r.mtx.RUnlock()
// Drain metricChan in case of premature return. wg.Add(goroutineBudget)
collectWorker := func() {
for {
select {
case collector := <-checkedCollectors:
collector.Collect(checkedMetricChan)
case collector := <-uncheckedCollectors:
collector.Collect(uncheckedMetricChan)
default:
return
}
wg.Done()
}
}
// Start the first worker now to make sure at least one is running.
go collectWorker()
goroutineBudget--
// Close checkedMetricChan and uncheckedMetricChan once all collectors
// are collected.
go func() {
wg.Wait()
close(checkedMetricChan)
close(uncheckedMetricChan)
}()
// 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 {
}
} }
}() }()
// Gather. // Copy the channel references so we can nil them out later to remove
for metric := range metricChan { // them from the select statements below.
// This could be done concurrently, too, but it required locking cmc := checkedMetricChan
// of metricFamiliesByName (and of metricHashes if checks are umc := uncheckedMetricChan
// enabled). Most likely not worth it.
desc := metric.Desc() for {
dtoMetric := &dto.Metric{} select {
if err := metric.Write(dtoMetric); err != nil { case metric, ok := <-cmc:
errs = append(errs, fmt.Errorf( if !ok {
"error collecting metric %v: %s", desc, err, cmc = nil
break
}
errs.Append(processMetric(
metric, metricFamiliesByName,
metricHashes,
registeredDescIDs,
)) ))
continue case metric, ok := <-umc:
if !ok {
umc = nil
break
}
errs.Append(processMetric(
metric, metricFamiliesByName,
metricHashes,
nil,
))
default:
if goroutineBudget <= 0 || len(checkedCollectors)+len(uncheckedCollectors) == 0 {
// All collectors are already being worked on or
// we have already as many goroutines started as
// there are collectors. Do the same as above,
// just without the default.
select {
case metric, ok := <-cmc:
if !ok {
cmc = nil
break
}
errs.Append(processMetric(
metric, metricFamiliesByName,
metricHashes,
registeredDescIDs,
))
case metric, ok := <-umc:
if !ok {
umc = nil
break
}
errs.Append(processMetric(
metric, metricFamiliesByName,
metricHashes,
nil,
))
}
break
}
// Start more workers.
go collectWorker()
goroutineBudget--
runtime.Gosched()
} }
metricFamily, ok := metricFamiliesByName[desc.fqName] // Once both checkedMetricChan and uncheckdMetricChan are closed
if ok { // and drained, the contraption above will nil out cmc and umc,
if metricFamily.GetHelp() != desc.help { // and then we can leave the collect loop here.
errs = append(errs, fmt.Errorf( if cmc == nil && umc == nil {
"collected metric %s %s has help %q but should have %q", break
desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
))
continue
}
// TODO(beorn7): Simplify switch once Desc has type.
switch metricFamily.GetType() {
case dto.MetricType_COUNTER:
if dtoMetric.Counter == nil {
errs = append(errs, fmt.Errorf(
"collected metric %s %s should be a Counter",
desc.fqName, dtoMetric,
))
continue
}
case dto.MetricType_GAUGE:
if dtoMetric.Gauge == nil {
errs = append(errs, fmt.Errorf(
"collected metric %s %s should be a Gauge",
desc.fqName, dtoMetric,
))
continue
}
case dto.MetricType_SUMMARY:
if dtoMetric.Summary == nil {
errs = append(errs, fmt.Errorf(
"collected metric %s %s should be a Summary",
desc.fqName, dtoMetric,
))
continue
}
case dto.MetricType_UNTYPED:
if dtoMetric.Untyped == nil {
errs = append(errs, fmt.Errorf(
"collected metric %s %s should be Untyped",
desc.fqName, dtoMetric,
))
continue
}
case dto.MetricType_HISTOGRAM:
if dtoMetric.Histogram == nil {
errs = append(errs, fmt.Errorf(
"collected metric %s %s should be a Histogram",
desc.fqName, dtoMetric,
))
continue
}
default:
panic("encountered MetricFamily with invalid type")
}
} else {
metricFamily = &dto.MetricFamily{}
metricFamily.Name = proto.String(desc.fqName)
metricFamily.Help = proto.String(desc.help)
// TODO(beorn7): Simplify switch once Desc has type.
switch {
case dtoMetric.Gauge != nil:
metricFamily.Type = dto.MetricType_GAUGE.Enum()
case dtoMetric.Counter != nil:
metricFamily.Type = dto.MetricType_COUNTER.Enum()
case dtoMetric.Summary != nil:
metricFamily.Type = dto.MetricType_SUMMARY.Enum()
case dtoMetric.Untyped != nil:
metricFamily.Type = dto.MetricType_UNTYPED.Enum()
case dtoMetric.Histogram != nil:
metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
default:
errs = append(errs, fmt.Errorf(
"empty metric collected: %s", dtoMetric,
))
continue
}
metricFamiliesByName[desc.fqName] = metricFamily
} }
if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes, dimHashes); err != nil {
errs = append(errs, err)
continue
}
if r.pedanticChecksEnabled {
// Is the desc registered at all?
if _, exist := registeredDescIDs[desc.id]; !exist {
errs = append(errs, fmt.Errorf(
"collected metric %s %s with unregistered descriptor %s",
metricFamily.GetName(), dtoMetric, desc,
))
continue
}
if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
errs = append(errs, err)
continue
}
}
metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
} }
return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap() return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
}
// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the
// Prometheus text format, and writes it to a temporary file. Upon success, the
// temporary file is renamed to the provided filename.
//
// This is intended for use with the textfile collector of the node exporter.
// Note that the node exporter expects the filename to be suffixed with ".prom".
func WriteToTextfile(filename string, g Gatherer) error {
tmp, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
if err != nil {
return err
}
defer os.Remove(tmp.Name())
mfs, err := g.Gather()
if err != nil {
return err
}
for _, mf := range mfs {
if _, err := expfmt.MetricFamilyToText(tmp, mf); err != nil {
return err
}
}
if err := tmp.Close(); err != nil {
return err
}
if err := os.Chmod(tmp.Name(), 0644); err != nil {
return err
}
return os.Rename(tmp.Name(), filename)
}
// processMetric is an internal helper method only used by the Gather method.
func processMetric(
metric Metric,
metricFamiliesByName map[string]*dto.MetricFamily,
metricHashes map[uint64]struct{},
registeredDescIDs map[uint64]struct{},
) error {
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{}
if err := metric.Write(dtoMetric); err != nil {
return fmt.Errorf("error collecting metric %v: %s", desc, err)
}
metricFamily, ok := metricFamiliesByName[desc.fqName]
if ok { // Existing name.
if metricFamily.GetHelp() != desc.help {
return fmt.Errorf(
"collected metric %s %s has help %q but should have %q",
desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
)
}
// TODO(beorn7): Simplify switch once Desc has type.
switch metricFamily.GetType() {
case dto.MetricType_COUNTER:
if dtoMetric.Counter == nil {
return fmt.Errorf(
"collected metric %s %s should be a Counter",
desc.fqName, dtoMetric,
)
}
case dto.MetricType_GAUGE:
if dtoMetric.Gauge == nil {
return fmt.Errorf(
"collected metric %s %s should be a Gauge",
desc.fqName, dtoMetric,
)
}
case dto.MetricType_SUMMARY:
if dtoMetric.Summary == nil {
return fmt.Errorf(
"collected metric %s %s should be a Summary",
desc.fqName, dtoMetric,
)
}
case dto.MetricType_UNTYPED:
if dtoMetric.Untyped == nil {
return fmt.Errorf(
"collected metric %s %s should be Untyped",
desc.fqName, dtoMetric,
)
}
case dto.MetricType_HISTOGRAM:
if dtoMetric.Histogram == nil {
return fmt.Errorf(
"collected metric %s %s should be a Histogram",
desc.fqName, dtoMetric,
)
}
default:
panic("encountered MetricFamily with invalid type")
}
} else { // New name.
metricFamily = &dto.MetricFamily{}
metricFamily.Name = proto.String(desc.fqName)
metricFamily.Help = proto.String(desc.help)
// TODO(beorn7): Simplify switch once Desc has type.
switch {
case dtoMetric.Gauge != nil:
metricFamily.Type = dto.MetricType_GAUGE.Enum()
case dtoMetric.Counter != nil:
metricFamily.Type = dto.MetricType_COUNTER.Enum()
case dtoMetric.Summary != nil:
metricFamily.Type = dto.MetricType_SUMMARY.Enum()
case dtoMetric.Untyped != nil:
metricFamily.Type = dto.MetricType_UNTYPED.Enum()
case dtoMetric.Histogram != nil:
metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
default:
return fmt.Errorf("empty metric collected: %s", dtoMetric)
}
if err := checkSuffixCollisions(metricFamily, metricFamiliesByName); err != nil {
return err
}
metricFamiliesByName[desc.fqName] = metricFamily
}
if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes); err != nil {
return err
}
if registeredDescIDs != nil {
// Is the desc registered at all?
if _, exist := registeredDescIDs[desc.id]; !exist {
return fmt.Errorf(
"collected metric %s %s with unregistered descriptor %s",
metricFamily.GetName(), dtoMetric, desc,
)
}
if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
return err
}
}
metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
return nil
} }
// Gatherers is a slice of Gatherer instances that implements the Gatherer // Gatherers is a slice of Gatherer instances that implements the Gatherer
// interface itself. Its Gather method calls Gather on all Gatherers in the // interface itself. Its Gather method calls Gather on all Gatherers in the
// slice in order and returns the merged results. Errors returned from the // slice in order and returns the merged results. Errors returned from the
// Gather calles are all returned in a flattened MultiError. Duplicate and // Gather calls are all returned in a flattened MultiError. Duplicate and
// inconsistent Metrics are skipped (first occurrence in slice order wins) and // inconsistent Metrics are skipped (first occurrence in slice order wins) and
// reported in the returned error. // reported in the returned error.
// //
@ -538,7 +700,6 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
var ( var (
metricFamiliesByName = map[string]*dto.MetricFamily{} metricFamiliesByName = map[string]*dto.MetricFamily{}
metricHashes = map[uint64]struct{}{} metricHashes = map[uint64]struct{}{}
dimHashes = map[string]uint64{}
errs MultiError // The collected errors to return in the end. errs MultiError // The collected errors to return in the end.
) )
@ -575,10 +736,14 @@ 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 {
if err := checkMetricConsistency(existingMF, m, metricHashes, dimHashes); err != nil { if err := checkMetricConsistency(existingMF, m, metricHashes); err != nil {
errs = append(errs, err) errs = append(errs, err)
continue continue
} }
@ -586,88 +751,80 @@ 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
// provided MetricFamily. It also hashed the Metric labels and the MetricFamily // provided MetricFamily. It also hashes the Metric labels and the MetricFamily
// name. If the resulting hash is alread in the provided metricHashes, an error // name. If the resulting hash is already in the provided metricHashes, an error
// is returned. If not, it is added to metricHashes. The provided dimHashes maps // is returned. If not, it is added to metricHashes.
// MetricFamily names to their dimHash (hashed sorted label names). If dimHashes
// doesn't yet contain a hash for the provided MetricFamily, it is
// added. Otherwise, an error is returned if the existing dimHashes in not equal
// the calculated dimHash.
func checkMetricConsistency( func checkMetricConsistency(
metricFamily *dto.MetricFamily, metricFamily *dto.MetricFamily,
dtoMetric *dto.Metric, dtoMetric *dto.Metric,
metricHashes map[uint64]struct{}, metricHashes map[uint64]struct{},
dimHashes map[string]uint64,
) error { ) error {
name := metricFamily.GetName()
// Type consistency with metric family. // Type consistency with metric family.
if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil || if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil || metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
@ -675,47 +832,65 @@ 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(), name, dtoMetric, metricFamily.GetType(),
) )
} }
previousLabelName := ""
for _, labelPair := range dtoMetric.GetLabel() { for _, labelPair := range dtoMetric.GetLabel() {
if !utf8.ValidString(*labelPair.Value) { labelName := labelPair.GetName()
return fmt.Errorf("collected metric's label %s is not utf8: %#v", *labelPair.Name, *labelPair.Value) if labelName == previousLabelName {
return fmt.Errorf(
"collected metric %q { %s} has two or more labels with the same name: %s",
name, dtoMetric, labelName,
)
} }
if !checkLabelName(labelName) {
return fmt.Errorf(
"collected metric %q { %s} has a label with an invalid name: %s",
name, dtoMetric, labelName,
)
}
if dtoMetric.Summary != nil && labelName == quantileLabel {
return fmt.Errorf(
"collected metric %q { %s} must not have an explicit %q label",
name, dtoMetric, quantileLabel,
)
}
if !utf8.ValidString(labelPair.GetValue()) {
return fmt.Errorf(
"collected metric %q { %s} has a label named %q whose value is not utf8: %#v",
name, dtoMetric, labelName, labelPair.GetValue())
}
previousLabelName = labelName
} }
// Is the metric unique (i.e. no other metric with the same name and the same label values)? // Is the metric unique (i.e. no other metric with the same name and the same labels)?
h := hashNew() h := hashNew()
h = hashAdd(h, metricFamily.GetName()) h = hashAdd(h, name)
h = hashAddByte(h, separatorByte) h = hashAddByte(h, separatorByte)
dh := hashNew()
// 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)) if !sort.IsSorted(labelPairSorter(dtoMetric.Label)) {
// We cannot sort dtoMetric.Label in place as it is immutable by contract.
copiedLabels := make([]*dto.LabelPair, len(dtoMetric.Label))
copy(copiedLabels, dtoMetric.Label)
sort.Sort(labelPairSorter(copiedLabels))
dtoMetric.Label = copiedLabels
}
for _, lp := range dtoMetric.Label { for _, lp := range dtoMetric.Label {
h = hashAdd(h, lp.GetName())
h = hashAddByte(h, separatorByte)
h = hashAdd(h, lp.GetValue()) h = hashAdd(h, lp.GetValue())
h = hashAddByte(h, separatorByte) h = hashAddByte(h, separatorByte)
dh = hashAdd(dh, lp.GetName())
dh = hashAddByte(dh, separatorByte)
} }
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, name, dtoMetric,
) )
} }
if dimHash, ok := dimHashes[metricFamily.GetName()]; ok {
if dimHash != dh {
return fmt.Errorf(
"collected metric %s %s has label dimensions inconsistent with previously collected metrics in the same metric family",
metricFamily.GetName(), dtoMetric,
)
}
} else {
dimHashes[metricFamily.GetName()] = dh
}
metricHashes[h] = struct{}{} metricHashes[h] = struct{}{}
return nil return nil
} }
@ -734,8 +909,8 @@ func checkDescConsistency(
} }
// Is the desc consistent with the content of the metric? // Is the desc consistent with the content of the metric?
lpsFromDesc := make([]*dto.LabelPair, 0, len(dtoMetric.Label)) lpsFromDesc := make([]*dto.LabelPair, len(desc.constLabelPairs), len(dtoMetric.Label))
lpsFromDesc = append(lpsFromDesc, desc.constLabelPairs...) copy(lpsFromDesc, desc.constLabelPairs)
for _, l := range desc.variableLabels { for _, l := range desc.variableLabels {
lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{ lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
Name: proto.String(l), Name: proto.String(l),
@ -747,7 +922,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

@ -16,8 +16,10 @@ package prometheus
import ( import (
"fmt" "fmt"
"math" "math"
"runtime"
"sort" "sort"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/beorn7/perks/quantile" "github.com/beorn7/perks/quantile"
@ -36,7 +38,10 @@ 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. // as rank estimations. However, the default behavior will change in the
// upcoming v1.0.0 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
// estimations explicitly.
// //
// Note that the rank estimations cannot be aggregated in a meaningful way with // Note that the rank estimations cannot be aggregated in a meaningful way with
// the Prometheus query language (i.e. you cannot average or add them). If you // the Prometheus query language (i.e. you cannot average or add them). If you
@ -56,7 +61,7 @@ type Summary interface {
// DefObjectives are the default Summary quantile values. // DefObjectives are the default Summary quantile values.
// //
// Deprecated: DefObjectives will not be used as the default objectives in // Deprecated: DefObjectives will not be used as the default objectives in
// v0.10 of the library. The default Summary will have no quantiles then. // v1.0.0 of the library. The default Summary will have no quantiles then.
var ( var (
DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001} DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
@ -78,8 +83,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. All other fields are // mandatory to set Name to a non-empty string. While all other fields are
// optional and can safely be left at their zero value. // optional and can safely be left at their zero value, it is recommended to set
// a help string and to explicitly set the Objectives field to the desired value
// as the default value will change in the upcoming v1.0.0 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
@ -90,29 +97,27 @@ 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.
Help string Help string
// ConstLabels are used to attach fixed labels to this // ConstLabels are used to attach fixed labels to this metric. Metrics
// Summary. Summaries with the same fully-qualified name must have the // with the same fully-qualified name must have the same label names in
// same label names in their ConstLabels. // their ConstLabels.
// //
// Note that in most cases, labels have a value that varies during the // Due to the way a Summary is represented in the Prometheus text format
// lifetime of a process. Those labels are usually managed with a // and how it is handled by the Prometheus server internally, “quantile”
// SummaryVec. ConstLabels serve only special purposes. One is for the // is an illegal label name. Construction of a Summary or SummaryVec
// special case where the value of a label does not change during the // will panic if this label name is used in ConstLabels.
// lifetime of a process, e.g. if the revision of the running binary is
// put into a label. Another, more advanced purpose is if more than one
// Collector needs to collect Summaries with the same fully-qualified
// name. In that case, those Summaries must differ in the values of
// their ConstLabels. See the Collector examples.
// //
// If the value of a label never changes (not even between binaries), // ConstLabels are only used rarely. In particular, do not use them to
// that label most likely should not be a label at all (but part of the // attach the same labels to all your metrics. Those use cases are
// metric name). // better covered by target labels set by the scraping Prometheus
// server, or by one specific metric (e.g. a build_info or a
// machine_role metric). See also
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
ConstLabels Labels ConstLabels Labels
// Objectives defines the quantile rank estimates with their respective // Objectives defines the quantile rank estimates with their respective
@ -122,9 +127,10 @@ type SummaryOpts struct {
// its zero value (i.e. nil). To create a Summary without Objectives, // its zero value (i.e. nil). To create a Summary without Objectives,
// set it to an empty map (i.e. map[float64]float64{}). // set it to an empty map (i.e. map[float64]float64{}).
// //
// Deprecated: Note that the current value of DefObjectives is // Note that the current value of DefObjectives is deprecated. It will
// deprecated. It will be replaced by an empty map in v0.10 of the // be replaced by an empty map in v1.0.0 of the library. Please
// library. Please explicitly set Objectives to the desired value. // explicitly set Objectives to the desired value to avoid problems
// during the transition.
Objectives map[float64]float64 Objectives map[float64]float64
// MaxAge defines the duration for which an observation stays relevant // MaxAge defines the duration for which an observation stays relevant
@ -148,7 +154,7 @@ type SummaryOpts struct {
BufCap uint32 BufCap uint32
} }
// Great fuck-up with the sliding-window decay algorithm... The Merge method of // Problem with the sliding-window decay algorithm... The Merge method of
// perk/quantile is actually not working as advertised - and it might be // perk/quantile is actually not working as advertised - and it might be
// unfixable, as the underlying algorithm is apparently not capable of merging // unfixable, as the underlying algorithm is apparently not capable of merging
// summaries in the first place. To avoid using Merge, we are currently adding // summaries in the first place. To avoid using Merge, we are currently adding
@ -178,7 +184,7 @@ func NewSummary(opts SummaryOpts) Summary {
func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary { func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
if len(desc.variableLabels) != len(labelValues) { if len(desc.variableLabels) != len(labelValues) {
panic(errInconsistentCardinality) panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
} }
for _, n := range desc.variableLabels { for _, n := range desc.variableLabels {
@ -211,6 +217,17 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
opts.BufCap = DefBufCap opts.BufCap = DefBufCap
} }
if len(opts.Objectives) == 0 {
// Use the lock-free implementation of a Summary without objectives.
s := &noObjectivesSummary{
desc: desc,
labelPairs: makeLabelPairs(desc, labelValues),
counts: [2]*summaryCounts{&summaryCounts{}, &summaryCounts{}},
}
s.init(s) // Init self-collection.
return s
}
s := &summary{ s := &summary{
desc: desc, desc: desc,
@ -379,6 +396,116 @@ func (s *summary) swapBufs(now time.Time) {
} }
} }
type summaryCounts struct {
// sumBits contains the bits of the float64 representing the sum of all
// observations. sumBits and count have to go first in the struct to
// guarantee alignment for atomic operations.
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
sumBits uint64
count uint64
}
type noObjectivesSummary struct {
// countAndHotIdx enables lock-free writes with use of atomic updates.
// The most significant bit is the hot index [0 or 1] of the count field
// below. Observe calls update the hot one. All remaining bits count the
// number of Observe calls. Observe starts by incrementing this counter,
// and finish by incrementing the count field in the respective
// summaryCounts, as a marker for completion.
//
// Calls of the Write method (which are non-mutating reads from the
// perspective of the summary) swap the hotcold under the writeMtx
// lock. A cooldown is awaited (while locked) by comparing the number of
// observations with the initiation count. Once they match, then the
// last observation on the now cool one has completed. All cool fields must
// be merged into the new hot before releasing writeMtx.
// Fields with atomic access first! See alignment constraint:
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
countAndHotIdx uint64
selfCollector
desc *Desc
writeMtx sync.Mutex // Only used in the Write method.
// Two counts, one is "hot" for lock-free observations, the other is
// "cold" for writing out a dto.Metric. It has to be an array of
// pointers to guarantee 64bit alignment of the histogramCounts, see
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
counts [2]*summaryCounts
labelPairs []*dto.LabelPair
}
func (s *noObjectivesSummary) Desc() *Desc {
return s.desc
}
func (s *noObjectivesSummary) Observe(v float64) {
// We increment h.countAndHotIdx so that the counter in the lower
// 63 bits gets incremented. At the same time, we get the new value
// back, which we can use to find the currently-hot counts.
n := atomic.AddUint64(&s.countAndHotIdx, 1)
hotCounts := s.counts[n>>63]
for {
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
break
}
}
// Increment count last as we take it as a signal that the observation
// is complete.
atomic.AddUint64(&hotCounts.count, 1)
}
func (s *noObjectivesSummary) Write(out *dto.Metric) error {
// For simplicity, we protect this whole method by a mutex. It is not in
// the hot path, i.e. Observe is called much more often than Write. The
// complication of making Write lock-free isn't worth it, if possible at
// all.
s.writeMtx.Lock()
defer s.writeMtx.Unlock()
// Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
// without touching the count bits. See the struct comments for a full
// description of the algorithm.
n := atomic.AddUint64(&s.countAndHotIdx, 1<<63)
// count is contained unchanged in the lower 63 bits.
count := n & ((1 << 63) - 1)
// The most significant bit tells us which counts is hot. The complement
// is thus the cold one.
hotCounts := s.counts[n>>63]
coldCounts := s.counts[(^n)>>63]
// Await cooldown.
for count != atomic.LoadUint64(&coldCounts.count) {
runtime.Gosched() // Let observations get work done.
}
sum := &dto.Summary{
SampleCount: proto.Uint64(count),
SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
}
out.Summary = sum
out.Label = s.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) + sum.GetSampleSum())
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
atomic.StoreUint64(&coldCounts.sumBits, 0)
break
}
}
return nil
}
type quantSort []*dto.Quantile type quantSort []*dto.Quantile
func (s quantSort) Len() int { func (s quantSort) Len() int {
@ -404,7 +531,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,
@ -428,13 +564,13 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
// //
// Keeping the Summary for later use is possible (and should be considered if // Keeping the Summary for later use is possible (and should be considered if
// performance is critical), but keep in mind that Reset, DeleteLabelValues and // performance is critical), but keep in mind that Reset, DeleteLabelValues and
// Delete can be used to delete the Summary from the SummaryVec. In that case, the // Delete can be used to delete the Summary from the SummaryVec. In that case,
// Summary will still exist, but it will not be exported anymore, even if a // the Summary will still exist, but it will not be exported anymore, even if a
// Summary with the same label values is created later. See also the CounterVec // Summary with the same label values is created later. See also the CounterVec
// example. // example.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of VariableLabels in Desc. // number of VariableLabels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
@ -442,8 +578,8 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
// latter has a much more readable (albeit more verbose) syntax, but it comes // latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the GaugeVec example. // See also the GaugeVec example.
func (m *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) { func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
metric, err := m.metricVec.getMetricWithLabelValues(lvs...) metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
@ -457,13 +593,13 @@ func (m *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
// the same as for GetMetricWithLabelValues. // the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc. // with those of the VariableLabels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (m *SummaryVec) GetMetricWith(labels Labels) (Observer, error) { func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
metric, err := m.metricVec.getMetricWith(labels) metric, err := v.metricVec.getMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
@ -471,18 +607,57 @@ func (m *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
} }
// WithLabelValues works as GetMetricWithLabelValues, but panics where // WithLabelValues works as GetMetricWithLabelValues, but panics where
// GetMetricWithLabelValues would have returned an error. By not returning an // GetMetricWithLabelValues would have returned an error. Not returning an
// error, WithLabelValues allows shortcuts like // error allows shortcuts like
// myVec.WithLabelValues("404", "GET").Observe(42.21) // myVec.WithLabelValues("404", "GET").Observe(42.21)
func (m *SummaryVec) WithLabelValues(lvs ...string) Observer { func (v *SummaryVec) WithLabelValues(lvs ...string) Observer {
return m.metricVec.withLabelValues(lvs...).(Observer) s, err := v.GetMetricWithLabelValues(lvs...)
if err != nil {
panic(err)
}
return s
} }
// With works as GetMetricWith, but panics where GetMetricWithLabels would have // With works as GetMetricWith, but panics where GetMetricWithLabels would have
// returned an error. By not returning an error, With allows shortcuts like // returned an error. Not returning an error allows shortcuts like
// myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21) // myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
func (m *SummaryVec) With(labels Labels) Observer { func (v *SummaryVec) With(labels Labels) Observer {
return m.metricVec.with(labels).(Observer) s, err := v.GetMetricWith(labels)
if err != nil {
panic(err)
}
return s
}
// CurryWith returns a vector curried with the provided labels, i.e. the
// returned vector has those labels pre-set for all labeled operations performed
// on it. The cardinality of the curried vector is reduced accordingly. The
// order of the remaining labels stays the same (just with the curried labels
// taken out of the sequence which is relevant for the
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
// vector, but only with labels not yet used for currying before.
//
// The metrics contained in the SummaryVec are shared between the curried and
// uncurried vectors. They are just accessed differently. Curried and uncurried
// vectors behave identically in terms of collection. Only one must be
// registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector.
func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) {
vec, err := v.curryWith(labels)
if vec != nil {
return &SummaryVec{vec}, err
}
return nil, err
}
// MustCurryWith works as CurryWith but panics where CurryWith would have
// returned an error.
func (v *SummaryVec) MustCurryWith(labels Labels) ObserverVec {
vec, err := v.CurryWith(labels)
if err != nil {
panic(err)
}
return vec
} }
type constSummary struct { type constSummary struct {
@ -535,7 +710,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,
@ -543,6 +718,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

@ -39,13 +39,16 @@ func NewTimer(o Observer) *Timer {
// ObserveDuration records the duration passed since the Timer was created with // ObserveDuration records the duration passed since the Timer was created with
// NewTimer. It calls the Observe method of the Observer provided during // NewTimer. It calls the Observe method of the Observer provided during
// construction with the duration in seconds as an argument. ObserveDuration is // construction with the duration in seconds as an argument. The observed
// usually called with a defer statement. // duration is also returned. ObserveDuration is usually called with a defer
// statement.
// //
// Note that this method is only guaranteed to never observe negative durations // Note that this method is only guaranteed to never observe negative durations
// if used with Go1.9+. // if used with Go1.9+.
func (t *Timer) ObserveDuration() { func (t *Timer) ObserveDuration() time.Duration {
d := time.Since(t.begin)
if t.observer != nil { if t.observer != nil {
t.observer.Observe(time.Since(t.begin).Seconds()) t.observer.Observe(d.Seconds())
} }
return d
} }

View file

@ -15,14 +15,11 @@ package prometheus
import ( import (
"fmt" "fmt"
"math"
"sort" "sort"
"sync/atomic"
"time"
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.
@ -36,79 +33,6 @@ const (
UntypedValue UntypedValue
) )
// value is a generic metric for simple values. It implements Metric, Collector,
// Counter, Gauge, and Untyped. Its effective type is determined by
// ValueType. This is a low-level building block used by the library to back the
// implementations of Counter, Gauge, and Untyped.
type value struct {
// valBits contains the bits of the represented float64 value. It has
// to go first in the struct to guarantee alignment for atomic
// operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
valBits uint64
selfCollector
desc *Desc
valType ValueType
labelPairs []*dto.LabelPair
}
// newValue returns a newly allocated value with the given Desc, ValueType,
// sample value and label values. It panics if the number of label
// values is different from the number of variable labels in Desc.
func newValue(desc *Desc, valueType ValueType, val float64, labelValues ...string) *value {
if len(labelValues) != len(desc.variableLabels) {
panic(errInconsistentCardinality)
}
result := &value{
desc: desc,
valType: valueType,
valBits: math.Float64bits(val),
labelPairs: makeLabelPairs(desc, labelValues),
}
result.init(result)
return result
}
func (v *value) Desc() *Desc {
return v.desc
}
func (v *value) Set(val float64) {
atomic.StoreUint64(&v.valBits, math.Float64bits(val))
}
func (v *value) SetToCurrentTime() {
v.Set(float64(time.Now().UnixNano()) / 1e9)
}
func (v *value) Inc() {
v.Add(1)
}
func (v *value) Dec() {
v.Add(-1)
}
func (v *value) Add(val float64) {
for {
oldBits := atomic.LoadUint64(&v.valBits)
newBits := math.Float64bits(math.Float64frombits(oldBits) + val)
if atomic.CompareAndSwapUint64(&v.valBits, oldBits, newBits) {
return
}
}
}
func (v *value) Sub(val float64) {
v.Add(val * -1)
}
func (v *value) Write(out *dto.Metric) error {
val := math.Float64frombits(atomic.LoadUint64(&v.valBits))
return populateMetric(v.valType, val, v.labelPairs, out)
}
// valueFunc is a generic metric for simple values retrieved on collect time // valueFunc is a generic metric for simple values retrieved on collect time
// from a function. It implements Metric and Collector. Its effective type is // from a function. It implements Metric and Collector. Its effective type is
// determined by ValueType. This is a low-level building block used by the // determined by ValueType. This is a low-level building block used by the
@ -153,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
} }
@ -228,9 +156,7 @@ func makeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
Value: proto.String(labelValues[i]), Value: proto.String(labelValues[i]),
}) })
} }
for _, lp := range desc.constLabelPairs { labelPairs = append(labelPairs, desc.constLabelPairs...)
labelPairs = append(labelPairs, lp) sort.Sort(labelPairSorter(labelPairs))
}
sort.Sort(LabelPairSorter(labelPairs))
return labelPairs return labelPairs
} }

View file

@ -23,88 +23,31 @@ import (
// metricVec is a Collector to bundle metrics of the same name that differ in // metricVec is a Collector to bundle metrics of the same name that differ in
// their label values. metricVec is not used directly (and therefore // their label values. metricVec is not used directly (and therefore
// unexported). It is used as a building block for implementations of vectors of // unexported). It is used as a building block for implementations of vectors of
// a given metric type, like GaugeVec, CounterVec, SummaryVec, HistogramVec, and // a given metric type, like GaugeVec, CounterVec, SummaryVec, and HistogramVec.
// UntypedVec. // It also handles label currying. It uses basicMetricVec internally.
type metricVec struct { type metricVec struct {
mtx sync.RWMutex // Protects the children. *metricMap
children map[uint64][]metricWithLabelValues
desc *Desc
newMetric func(labelValues ...string) Metric curry []curriedLabelValue
hashAdd func(h uint64, s string) uint64 // replace hash function for testing collision handling
// hashAdd and hashAddByte can be replaced for testing collision handling.
hashAdd func(h uint64, s string) uint64
hashAddByte func(h uint64, b byte) uint64 hashAddByte func(h uint64, b byte) uint64
} }
// newMetricVec returns an initialized metricVec. // newMetricVec returns an initialized metricVec.
func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *metricVec { func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *metricVec {
return &metricVec{ return &metricVec{
children: map[uint64][]metricWithLabelValues{}, metricMap: &metricMap{
desc: desc, metrics: map[uint64][]metricWithLabelValues{},
newMetric: newMetric, desc: desc,
newMetric: newMetric,
},
hashAdd: hashAdd, hashAdd: hashAdd,
hashAddByte: hashAddByte, hashAddByte: hashAddByte,
} }
} }
// metricWithLabelValues provides the metric and its label values for
// disambiguation on hash collision.
type metricWithLabelValues struct {
values []string
metric Metric
}
// Describe implements Collector. The length of the returned slice
// is always one.
func (m *metricVec) Describe(ch chan<- *Desc) {
ch <- m.desc
}
// Collect implements Collector.
func (m *metricVec) Collect(ch chan<- Metric) {
m.mtx.RLock()
defer m.mtx.RUnlock()
for _, metrics := range m.children {
for _, metric := range metrics {
ch <- metric.metric
}
}
}
func (m *metricVec) getMetricWithLabelValues(lvs ...string) (Metric, error) {
h, err := m.hashLabelValues(lvs)
if err != nil {
return nil, err
}
return m.getOrCreateMetricWithLabelValues(h, lvs), nil
}
func (m *metricVec) getMetricWith(labels Labels) (Metric, error) {
h, err := m.hashLabels(labels)
if err != nil {
return nil, err
}
return m.getOrCreateMetricWithLabels(h, labels), nil
}
func (m *metricVec) withLabelValues(lvs ...string) Metric {
metric, err := m.getMetricWithLabelValues(lvs...)
if err != nil {
panic(err)
}
return metric
}
func (m *metricVec) with(labels Labels) Metric {
metric, err := m.getMetricWith(labels)
if err != nil {
panic(err)
}
return metric
}
// DeleteLabelValues removes the metric where the variable labels are the same // DeleteLabelValues removes the metric where the variable labels are the same
// as those passed in as labels (same order as the VariableLabels in Desc). It // as those passed in as labels (same order as the VariableLabels in Desc). It
// returns true if a metric was deleted. // returns true if a metric was deleted.
@ -121,14 +64,12 @@ func (m *metricVec) with(labels Labels) Metric {
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the CounterVec example. // See also the CounterVec example.
func (m *metricVec) DeleteLabelValues(lvs ...string) bool { func (m *metricVec) DeleteLabelValues(lvs ...string) bool {
m.mtx.Lock()
defer m.mtx.Unlock()
h, err := m.hashLabelValues(lvs) h, err := m.hashLabelValues(lvs)
if err != nil { if err != nil {
return false return false
} }
return m.deleteByHashWithLabelValues(h, lvs)
return m.metricMap.deleteByHashWithLabelValues(h, lvs, m.curry)
} }
// Delete deletes the metric where the variable labels are the same as those // Delete deletes the metric where the variable labels are the same as those
@ -142,35 +83,190 @@ func (m *metricVec) DeleteLabelValues(lvs ...string) bool {
// This method is used for the same purpose as DeleteLabelValues(...string). See // This method is used for the same purpose as DeleteLabelValues(...string). See
// there for pros and cons of the two methods. // there for pros and cons of the two methods.
func (m *metricVec) Delete(labels Labels) bool { func (m *metricVec) Delete(labels Labels) bool {
m.mtx.Lock()
defer m.mtx.Unlock()
h, err := m.hashLabels(labels) h, err := m.hashLabels(labels)
if err != nil { if err != nil {
return false return false
} }
return m.deleteByHashWithLabels(h, labels) return m.metricMap.deleteByHashWithLabels(h, labels, m.curry)
}
func (m *metricVec) curryWith(labels Labels) (*metricVec, error) {
var (
newCurry []curriedLabelValue
oldCurry = m.curry
iCurry int
)
for i, label := range m.desc.variableLabels {
val, ok := labels[label]
if iCurry < len(oldCurry) && oldCurry[iCurry].index == i {
if ok {
return nil, fmt.Errorf("label name %q is already curried", label)
}
newCurry = append(newCurry, oldCurry[iCurry])
iCurry++
} else {
if !ok {
continue // Label stays uncurried.
}
newCurry = append(newCurry, curriedLabelValue{i, val})
}
}
if l := len(oldCurry) + len(labels) - len(newCurry); l > 0 {
return nil, fmt.Errorf("%d unknown label(s) found during currying", l)
}
return &metricVec{
metricMap: m.metricMap,
curry: newCurry,
hashAdd: m.hashAdd,
hashAddByte: m.hashAddByte,
}, nil
}
func (m *metricVec) getMetricWithLabelValues(lvs ...string) (Metric, error) {
h, err := m.hashLabelValues(lvs)
if err != nil {
return nil, err
}
return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil
}
func (m *metricVec) getMetricWith(labels Labels) (Metric, error) {
h, err := m.hashLabels(labels)
if err != nil {
return nil, err
}
return m.metricMap.getOrCreateMetricWithLabels(h, labels, m.curry), nil
}
func (m *metricVec) hashLabelValues(vals []string) (uint64, error) {
if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil {
return 0, err
}
var (
h = hashNew()
curry = m.curry
iVals, iCurry int
)
for i := 0; i < len(m.desc.variableLabels); i++ {
if iCurry < len(curry) && curry[iCurry].index == i {
h = m.hashAdd(h, curry[iCurry].value)
iCurry++
} else {
h = m.hashAdd(h, vals[iVals])
iVals++
}
h = m.hashAddByte(h, model.SeparatorByte)
}
return h, nil
}
func (m *metricVec) hashLabels(labels Labels) (uint64, error) {
if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil {
return 0, err
}
var (
h = hashNew()
curry = m.curry
iCurry int
)
for i, label := range m.desc.variableLabels {
val, ok := labels[label]
if iCurry < len(curry) && curry[iCurry].index == i {
if ok {
return 0, fmt.Errorf("label name %q is already curried", label)
}
h = m.hashAdd(h, curry[iCurry].value)
iCurry++
} else {
if !ok {
return 0, fmt.Errorf("label name %q missing in label map", label)
}
h = m.hashAdd(h, val)
}
h = m.hashAddByte(h, model.SeparatorByte)
}
return h, nil
}
// metricWithLabelValues provides the metric and its label values for
// disambiguation on hash collision.
type metricWithLabelValues struct {
values []string
metric Metric
}
// curriedLabelValue sets the curried value for a label at the given index.
type curriedLabelValue struct {
index int
value string
}
// metricMap is a helper for metricVec and shared between differently curried
// metricVecs.
type metricMap struct {
mtx sync.RWMutex // Protects metrics.
metrics map[uint64][]metricWithLabelValues
desc *Desc
newMetric func(labelValues ...string) Metric
}
// Describe implements Collector. It will send exactly one Desc to the provided
// channel.
func (m *metricMap) Describe(ch chan<- *Desc) {
ch <- m.desc
}
// Collect implements Collector.
func (m *metricMap) Collect(ch chan<- Metric) {
m.mtx.RLock()
defer m.mtx.RUnlock()
for _, metrics := range m.metrics {
for _, metric := range metrics {
ch <- metric.metric
}
}
}
// Reset deletes all metrics in this vector.
func (m *metricMap) Reset() {
m.mtx.Lock()
defer m.mtx.Unlock()
for h := range m.metrics {
delete(m.metrics, h)
}
} }
// deleteByHashWithLabelValues removes the metric from the hash bucket h. If // deleteByHashWithLabelValues removes the metric from the hash bucket h. If
// there are multiple matches in the bucket, use lvs to select a metric and // there are multiple matches in the bucket, use lvs to select a metric and
// remove only that metric. // remove only that metric.
func (m *metricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool { func (m *metricMap) deleteByHashWithLabelValues(
metrics, ok := m.children[h] h uint64, lvs []string, curry []curriedLabelValue,
) bool {
m.mtx.Lock()
defer m.mtx.Unlock()
metrics, ok := m.metrics[h]
if !ok { if !ok {
return false return false
} }
i := m.findMetricWithLabelValues(metrics, lvs) i := findMetricWithLabelValues(metrics, lvs, curry)
if i >= len(metrics) { if i >= len(metrics) {
return false return false
} }
if len(metrics) > 1 { if len(metrics) > 1 {
m.children[h] = append(metrics[:i], metrics[i+1:]...) m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
} else { } else {
delete(m.children, h) delete(m.metrics, h)
} }
return true return true
} }
@ -178,71 +274,38 @@ func (m *metricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool {
// deleteByHashWithLabels removes the metric from the hash bucket h. If there // deleteByHashWithLabels removes the metric from the hash bucket h. If there
// are multiple matches in the bucket, use lvs to select a metric and remove // are multiple matches in the bucket, use lvs to select a metric and remove
// only that metric. // only that metric.
func (m *metricVec) deleteByHashWithLabels(h uint64, labels Labels) bool { func (m *metricMap) deleteByHashWithLabels(
metrics, ok := m.children[h] h uint64, labels Labels, curry []curriedLabelValue,
) bool {
m.mtx.Lock()
defer m.mtx.Unlock()
metrics, ok := m.metrics[h]
if !ok { if !ok {
return false return false
} }
i := m.findMetricWithLabels(metrics, labels) i := findMetricWithLabels(m.desc, metrics, labels, curry)
if i >= len(metrics) { if i >= len(metrics) {
return false return false
} }
if len(metrics) > 1 { if len(metrics) > 1 {
m.children[h] = append(metrics[:i], metrics[i+1:]...) m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
} else { } else {
delete(m.children, h) delete(m.metrics, h)
} }
return true return true
} }
// Reset deletes all metrics in this vector.
func (m *metricVec) Reset() {
m.mtx.Lock()
defer m.mtx.Unlock()
for h := range m.children {
delete(m.children, h)
}
}
func (m *metricVec) hashLabelValues(vals []string) (uint64, error) {
if err := validateLabelValues(vals, len(m.desc.variableLabels)); err != nil {
return 0, err
}
h := hashNew()
for _, val := range vals {
h = m.hashAdd(h, val)
h = m.hashAddByte(h, model.SeparatorByte)
}
return h, nil
}
func (m *metricVec) hashLabels(labels Labels) (uint64, error) {
if err := validateValuesInLabels(labels, len(m.desc.variableLabels)); err != nil {
return 0, err
}
h := hashNew()
for _, label := range m.desc.variableLabels {
val, ok := labels[label]
if !ok {
return 0, fmt.Errorf("label name %q missing in label map", label)
}
h = m.hashAdd(h, val)
h = m.hashAddByte(h, model.SeparatorByte)
}
return h, nil
}
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value // getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
// or creates it and returns the new one. // or creates it and returns the new one.
// //
// This function holds the mutex. // This function holds the mutex.
func (m *metricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string) Metric { func (m *metricMap) getOrCreateMetricWithLabelValues(
hash uint64, lvs []string, curry []curriedLabelValue,
) Metric {
m.mtx.RLock() m.mtx.RLock()
metric, ok := m.getMetricWithHashAndLabelValues(hash, lvs) metric, ok := m.getMetricWithHashAndLabelValues(hash, lvs, curry)
m.mtx.RUnlock() m.mtx.RUnlock()
if ok { if ok {
return metric return metric
@ -250,13 +313,11 @@ func (m *metricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string)
m.mtx.Lock() m.mtx.Lock()
defer m.mtx.Unlock() defer m.mtx.Unlock()
metric, ok = m.getMetricWithHashAndLabelValues(hash, lvs) metric, ok = m.getMetricWithHashAndLabelValues(hash, lvs, curry)
if !ok { if !ok {
// Copy to avoid allocation in case wo don't go down this code path. inlinedLVs := inlineLabelValues(lvs, curry)
copiedLVs := make([]string, len(lvs)) metric = m.newMetric(inlinedLVs...)
copy(copiedLVs, lvs) m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: inlinedLVs, metric: metric})
metric = m.newMetric(copiedLVs...)
m.children[hash] = append(m.children[hash], metricWithLabelValues{values: copiedLVs, metric: metric})
} }
return metric return metric
} }
@ -265,9 +326,11 @@ func (m *metricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string)
// or creates it and returns the new one. // or creates it and returns the new one.
// //
// This function holds the mutex. // This function holds the mutex.
func (m *metricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metric { func (m *metricMap) getOrCreateMetricWithLabels(
hash uint64, labels Labels, curry []curriedLabelValue,
) Metric {
m.mtx.RLock() m.mtx.RLock()
metric, ok := m.getMetricWithHashAndLabels(hash, labels) metric, ok := m.getMetricWithHashAndLabels(hash, labels, curry)
m.mtx.RUnlock() m.mtx.RUnlock()
if ok { if ok {
return metric return metric
@ -275,21 +338,23 @@ func (m *metricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metr
m.mtx.Lock() m.mtx.Lock()
defer m.mtx.Unlock() defer m.mtx.Unlock()
metric, ok = m.getMetricWithHashAndLabels(hash, labels) metric, ok = m.getMetricWithHashAndLabels(hash, labels, curry)
if !ok { if !ok {
lvs := m.extractLabelValues(labels) lvs := extractLabelValues(m.desc, labels, curry)
metric = m.newMetric(lvs...) metric = m.newMetric(lvs...)
m.children[hash] = append(m.children[hash], metricWithLabelValues{values: lvs, metric: metric}) m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: lvs, metric: metric})
} }
return metric return metric
} }
// getMetricWithHashAndLabelValues gets a metric while handling possible // getMetricWithHashAndLabelValues gets a metric while handling possible
// collisions in the hash space. Must be called while holding the read mutex. // collisions in the hash space. Must be called while holding the read mutex.
func (m *metricVec) getMetricWithHashAndLabelValues(h uint64, lvs []string) (Metric, bool) { func (m *metricMap) getMetricWithHashAndLabelValues(
metrics, ok := m.children[h] h uint64, lvs []string, curry []curriedLabelValue,
) (Metric, bool) {
metrics, ok := m.metrics[h]
if ok { if ok {
if i := m.findMetricWithLabelValues(metrics, lvs); i < len(metrics) { if i := findMetricWithLabelValues(metrics, lvs, curry); i < len(metrics) {
return metrics[i].metric, true return metrics[i].metric, true
} }
} }
@ -298,10 +363,12 @@ func (m *metricVec) getMetricWithHashAndLabelValues(h uint64, lvs []string) (Met
// getMetricWithHashAndLabels gets a metric while handling possible collisions in // getMetricWithHashAndLabels gets a metric while handling possible collisions in
// the hash space. Must be called while holding read mutex. // the hash space. Must be called while holding read mutex.
func (m *metricVec) getMetricWithHashAndLabels(h uint64, labels Labels) (Metric, bool) { func (m *metricMap) getMetricWithHashAndLabels(
metrics, ok := m.children[h] h uint64, labels Labels, curry []curriedLabelValue,
) (Metric, bool) {
metrics, ok := m.metrics[h]
if ok { if ok {
if i := m.findMetricWithLabels(metrics, labels); i < len(metrics) { if i := findMetricWithLabels(m.desc, metrics, labels, curry); i < len(metrics) {
return metrics[i].metric, true return metrics[i].metric, true
} }
} }
@ -310,9 +377,11 @@ func (m *metricVec) getMetricWithHashAndLabels(h uint64, labels Labels) (Metric,
// findMetricWithLabelValues returns the index of the matching metric or // findMetricWithLabelValues returns the index of the matching metric or
// len(metrics) if not found. // len(metrics) if not found.
func (m *metricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, lvs []string) int { func findMetricWithLabelValues(
metrics []metricWithLabelValues, lvs []string, curry []curriedLabelValue,
) int {
for i, metric := range metrics { for i, metric := range metrics {
if m.matchLabelValues(metric.values, lvs) { if matchLabelValues(metric.values, lvs, curry) {
return i return i
} }
} }
@ -321,32 +390,51 @@ func (m *metricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, l
// findMetricWithLabels returns the index of the matching metric or len(metrics) // findMetricWithLabels returns the index of the matching metric or len(metrics)
// if not found. // if not found.
func (m *metricVec) findMetricWithLabels(metrics []metricWithLabelValues, labels Labels) int { func findMetricWithLabels(
desc *Desc, metrics []metricWithLabelValues, labels Labels, curry []curriedLabelValue,
) int {
for i, metric := range metrics { for i, metric := range metrics {
if m.matchLabels(metric.values, labels) { if matchLabels(desc, metric.values, labels, curry) {
return i return i
} }
} }
return len(metrics) return len(metrics)
} }
func (m *metricVec) matchLabelValues(values []string, lvs []string) bool { func matchLabelValues(values []string, lvs []string, curry []curriedLabelValue) bool {
if len(values) != len(lvs) { if len(values) != len(lvs)+len(curry) {
return false return false
} }
var iLVs, iCurry int
for i, v := range values { for i, v := range values {
if v != lvs[i] { if iCurry < len(curry) && curry[iCurry].index == i {
if v != curry[iCurry].value {
return false
}
iCurry++
continue
}
if v != lvs[iLVs] {
return false return false
} }
iLVs++
} }
return true return true
} }
func (m *metricVec) matchLabels(values []string, labels Labels) bool { func matchLabels(desc *Desc, values []string, labels Labels, curry []curriedLabelValue) bool {
if len(labels) != len(values) { if len(values) != len(labels)+len(curry) {
return false return false
} }
for i, k := range m.desc.variableLabels { iCurry := 0
for i, k := range desc.variableLabels {
if iCurry < len(curry) && curry[iCurry].index == i {
if values[i] != curry[iCurry].value {
return false
}
iCurry++
continue
}
if values[i] != labels[k] { if values[i] != labels[k] {
return false return false
} }
@ -354,10 +442,31 @@ func (m *metricVec) matchLabels(values []string, labels Labels) bool {
return true return true
} }
func (m *metricVec) extractLabelValues(labels Labels) []string { func extractLabelValues(desc *Desc, labels Labels, curry []curriedLabelValue) []string {
labelValues := make([]string, len(labels)) labelValues := make([]string, len(labels)+len(curry))
for i, k := range m.desc.variableLabels { iCurry := 0
for i, k := range desc.variableLabels {
if iCurry < len(curry) && curry[iCurry].index == i {
labelValues[i] = curry[iCurry].value
iCurry++
continue
}
labelValues[i] = labels[k] labelValues[i] = labels[k]
} }
return labelValues return labelValues
} }
func inlineLabelValues(lvs []string, curry []curriedLabelValue) []string {
labelValues := make([]string, len(lvs)+len(curry))
var iCurry, iLVs int
for i := range labelValues {
if iCurry < len(curry) && curry[iCurry].index == i {
labelValues[i] = curry[iCurry].value
iCurry++
continue
}
labelValues[i] = lvs[iLVs]
iLVs++
}
return labelValues
}

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 +1,2 @@
* Tobias Schmidt <tobidt@gmail.com> * Johannes 'fish' Ziemke <github@freigeist.org> @discordianfish
* Paul Gier <pgier@redhat.com> @pgier

View file

@ -1,18 +1,29 @@
ci: fmt lint test # 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.
fmt: include Makefile.common
! gofmt -l *.go | read nothing
go vet
lint: %/.unpacked: %.ttar
go get github.com/golang/lint/golint @echo ">> extracting fixtures"
golint *.go ./ttar -C $(dir $*) -x -f $*.ttar
test: sysfs/fixtures/.unpacked
go test -v ./...
sysfs/fixtures/.unpacked: sysfs/fixtures.ttar
./ttar -C sysfs -x -f sysfs/fixtures.ttar
touch $@ touch $@
.PHONY: fmt lint test ci update_fixtures:
rm -vf fixtures/.unpacked
./ttar -c -f fixtures.ttar fixtures/
.PHONY: build
build:
.PHONY: test
test: fixtures/.unpacked common-test

272
vendor/github.com/prometheus/procfs/Makefile.common generated vendored Normal file
View file

@ -0,0 +1,272 @@
# Copyright 2018 The Prometheus Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# A common Makefile that includes rules to be reused in different prometheus projects.
# !!! Open PRs only against the prometheus/prometheus/Makefile.common repository!
# Example usage :
# Create the main Makefile in the root project directory.
# include Makefile.common
# customTarget:
# @echo ">> Running customTarget"
#
# Ensure GOBIN is not set during build so that promu is installed to the correct path
unexport GOBIN
GO ?= go
GOFMT ?= $(GO)fmt
FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH)))
GOOPTS ?=
GOHOSTOS ?= $(shell $(GO) env GOHOSTOS)
GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH)
GO_VERSION ?= $(shell $(GO) version)
GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION))
PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.')
GOVENDOR :=
GO111MODULE :=
ifeq (, $(PRE_GO_111))
ifneq (,$(wildcard go.mod))
# Enforce Go modules support just in case the directory is inside GOPATH (and for Travis CI).
GO111MODULE := on
ifneq (,$(wildcard vendor))
# Always use the local vendor/ directory to satisfy the dependencies.
GOOPTS := $(GOOPTS) -mod=vendor
endif
endif
else
ifneq (,$(wildcard go.mod))
ifneq (,$(wildcard vendor))
$(warning This repository requires Go >= 1.11 because of Go modules)
$(warning Some recipes may not work as expected as the current Go runtime is '$(GO_VERSION_NUMBER)')
endif
else
# This repository isn't using Go modules (yet).
GOVENDOR := $(FIRST_GOPATH)/bin/govendor
endif
endif
PROMU := $(FIRST_GOPATH)/bin/promu
pkgs = ./...
ifeq (arm, $(GOHOSTARCH))
GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM)
GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM)
else
GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
endif
PROMU_VERSION ?= 0.4.0
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
GOLANGCI_LINT :=
GOLANGCI_LINT_OPTS ?=
GOLANGCI_LINT_VERSION ?= v1.16.0
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
# windows isn't included here because of the path separator being different.
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386))
GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint
endif
endif
PREFIX ?= $(shell pwd)
BIN_DIR ?= $(shell pwd)
DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
DOCKER_REPO ?= prom
DOCKER_ARCHS ?= amd64
BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS))
PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS))
TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS))
ifeq ($(GOHOSTARCH),amd64)
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows))
# Only supported on amd64
test-flags := -race
endif
endif
# This rule is used to forward a target like "build" to "common-build". This
# allows a new "build" target to be defined in a Makefile which includes this
# one and override "common-build" without override warnings.
%: common-% ;
.PHONY: common-all
common-all: precheck style check_license lint unused build test
.PHONY: common-style
common-style:
@echo ">> checking code style"
@fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \
if [ -n "$${fmtRes}" ]; then \
echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \
echo "Please ensure you are using $$($(GO) version) for formatting code."; \
exit 1; \
fi
.PHONY: common-check_license
common-check_license:
@echo ">> checking license header"
@licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \
awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \
done); \
if [ -n "$${licRes}" ]; then \
echo "license header checking failed:"; echo "$${licRes}"; \
exit 1; \
fi
.PHONY: common-deps
common-deps:
@echo ">> getting dependencies"
ifdef GO111MODULE
GO111MODULE=$(GO111MODULE) $(GO) mod download
else
$(GO) get $(GOOPTS) -t ./...
endif
.PHONY: common-test-short
common-test-short:
@echo ">> running short tests"
GO111MODULE=$(GO111MODULE) $(GO) test -short $(GOOPTS) $(pkgs)
.PHONY: common-test
common-test:
@echo ">> running all tests"
GO111MODULE=$(GO111MODULE) $(GO) test $(test-flags) $(GOOPTS) $(pkgs)
.PHONY: common-format
common-format:
@echo ">> formatting code"
GO111MODULE=$(GO111MODULE) $(GO) fmt $(pkgs)
.PHONY: common-vet
common-vet:
@echo ">> vetting code"
GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs)
.PHONY: common-lint
common-lint: $(GOLANGCI_LINT)
ifdef GOLANGCI_LINT
@echo ">> running golangci-lint"
ifdef GO111MODULE
# 'go list' needs to be executed before staticcheck to prepopulate the modules cache.
# Otherwise staticcheck might fail randomly for some reason not yet explained.
GO111MODULE=$(GO111MODULE) $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null
GO111MODULE=$(GO111MODULE) $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs)
else
$(GOLANGCI_LINT) run $(pkgs)
endif
endif
# For backward-compatibility.
.PHONY: common-staticcheck
common-staticcheck: lint
.PHONY: common-unused
common-unused: $(GOVENDOR)
ifdef GOVENDOR
@echo ">> running check for unused packages"
@$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages'
else
ifdef GO111MODULE
@echo ">> running check for unused/missing packages in go.mod"
GO111MODULE=$(GO111MODULE) $(GO) mod tidy
ifeq (,$(wildcard vendor))
@git diff --exit-code -- go.sum go.mod
else
@echo ">> running check for unused packages in vendor/"
GO111MODULE=$(GO111MODULE) $(GO) mod vendor
@git diff --exit-code -- go.sum go.mod vendor/
endif
endif
endif
.PHONY: common-build
common-build: promu
@echo ">> building binaries"
GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX)
.PHONY: common-tarball
common-tarball: promu
@echo ">> building release tarball"
$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR)
.PHONY: common-docker $(BUILD_DOCKER_ARCHS)
common-docker: $(BUILD_DOCKER_ARCHS)
$(BUILD_DOCKER_ARCHS): common-docker-%:
docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \
--build-arg ARCH="$*" \
--build-arg OS="linux" \
.
.PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS)
common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
$(PUBLISH_DOCKER_ARCHS): common-docker-publish-%:
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)"
.PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS)
common-docker-tag-latest: $(TAG_DOCKER_ARCHS)
$(TAG_DOCKER_ARCHS): common-docker-tag-latest-%:
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
.PHONY: common-docker-manifest
common-docker-manifest:
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG))
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)"
.PHONY: promu
promu: $(PROMU)
$(PROMU):
$(eval PROMU_TMP := $(shell mktemp -d))
curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP)
mkdir -p $(FIRST_GOPATH)/bin
cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu
rm -r $(PROMU_TMP)
.PHONY: proto
proto:
@echo ">> generating code from proto files"
@./scripts/genproto.sh
ifdef GOLANGCI_LINT
$(GOLANGCI_LINT):
mkdir -p $(FIRST_GOPATH)/bin
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
endif
ifdef GOVENDOR
.PHONY: $(GOVENDOR)
$(GOVENDOR):
GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor
endif
.PHONY: precheck
precheck::
define PRECHECK_COMMAND_template =
precheck:: $(1)_precheck
PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1)))
.PHONY: $(1)_precheck
$(1)_precheck:
@if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \
echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \
exit 1; \
fi
endef

View file

@ -1,7 +1,7 @@
# procfs # procfs
This procfs package provides functions to retrieve system, kernel and process This procfs package provides functions to retrieve system, kernel and process
metrics from the pseudo-filesystem proc. metrics from the pseudo-filesystems /proc and /sys.
*WARNING*: This package is a work in progress. Its API may still break in *WARNING*: This package is a work in progress. Its API may still break in
backwards-incompatible ways without warnings. Use it at your own risk. backwards-incompatible ways without warnings. Use it at your own risk.
@ -9,3 +9,45 @@ backwards-incompatible ways without warnings. Use it at your own risk.
[![GoDoc](https://godoc.org/github.com/prometheus/procfs?status.png)](https://godoc.org/github.com/prometheus/procfs) [![GoDoc](https://godoc.org/github.com/prometheus/procfs?status.png)](https://godoc.org/github.com/prometheus/procfs)
[![Build Status](https://travis-ci.org/prometheus/procfs.svg?branch=master)](https://travis-ci.org/prometheus/procfs) [![Build Status](https://travis-ci.org/prometheus/procfs.svg?branch=master)](https://travis-ci.org/prometheus/procfs)
[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/procfs)](https://goreportcard.com/report/github.com/prometheus/procfs) [![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/procfs)](https://goreportcard.com/report/github.com/prometheus/procfs)
## Usage
The procfs library is organized by packages based on whether the gathered data is coming from
/proc, /sys, or both. Each package contains an `FS` type which represents the path to either /proc, /sys, or both. For example, current cpu statistics are gathered from
`/proc/stat` and are available via the root procfs package. First, the proc filesystem mount
point is initialized, and then the stat information is read.
```go
fs, err := procfs.NewFS("/proc")
stats, err := fs.Stat()
```
Some sub-packages such as `blockdevice`, require access to both the proc and sys filesystems.
```go
fs, err := blockdevice.NewFS("/proc", "/sys")
stats, err := fs.ProcDiskstats()
```
## Building and Testing
The procfs library is normally built as part of another application. However, when making
changes to the library, the `make test` command can be used to run the API test suite.
### Updating Test Fixtures
The procfs library includes a set of test fixtures which include many example files from
the `/proc` and `/sys` filesystems. These fixtures are included as a [ttar](https://github.com/ideaship/ttar) file
which is extracted automatically during testing. To add/update the test fixtures, first
ensure the `fixtures` directory is up to date by removing the existing directory and then
extracting the ttar file using `make fixtures/.unpacked` or just `make test`.
```bash
rm -rf fixtures
make test
```
Next, make the required changes to the extracted files in the `fixtures` directory. When
the changes are complete, run `make update_fixtures` to create a new `fixtures.ttar` file
based on the updated `fixtures` directory. And finally, verify the changes using
`git diff fixtures.ttar`.

View file

@ -31,19 +31,9 @@ type BuddyInfo struct {
Sizes []float64 Sizes []float64
} }
// NewBuddyInfo reads the buddyinfo statistics.
func NewBuddyInfo() ([]BuddyInfo, error) {
fs, err := NewFS(DefaultMountPoint)
if err != nil {
return nil, err
}
return fs.NewBuddyInfo()
}
// NewBuddyInfo reads the buddyinfo statistics from the specified `proc` filesystem. // NewBuddyInfo reads the buddyinfo statistics from the specified `proc` filesystem.
func (fs FS) NewBuddyInfo() ([]BuddyInfo, error) { func (fs FS) BuddyInfo() ([]BuddyInfo, error) {
file, err := os.Open(fs.Path("buddyinfo")) file, err := os.Open(fs.proc.Path("buddyinfo"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -62,7 +52,7 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) {
for scanner.Scan() { for scanner.Scan() {
var err error var err error
line := scanner.Text() line := scanner.Text()
parts := strings.Fields(string(line)) parts := strings.Fields(line)
if len(parts) < 4 { if len(parts) < 4 {
return nil, fmt.Errorf("invalid number of fields when parsing buddyinfo") return nil, fmt.Errorf("invalid number of fields when parsing buddyinfo")

1808
vendor/github.com/prometheus/procfs/fixtures.ttar generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,46 +1,43 @@
// 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 procfs package procfs
import ( import (
"fmt" "github.com/prometheus/procfs/internal/fs"
"os"
"path"
"github.com/prometheus/procfs/xfs"
) )
// FS represents the pseudo-filesystem proc, which provides an interface to // FS represents the pseudo-filesystem sys, which provides an interface to
// kernel data structures. // kernel data structures.
type FS string type FS struct {
proc fs.FS
}
// DefaultMountPoint is the common mount point of the proc filesystem. // DefaultMountPoint is the common mount point of the proc filesystem.
const DefaultMountPoint = "/proc" const DefaultMountPoint = fs.DefaultProcMountPoint
// NewFS returns a new FS mounted under the given mountPoint. It will error // NewDefaultFS returns a new proc FS mounted under the default proc mountPoint.
// if the mount point can't be read. // It will error if the mount point directory can't be read or is a file.
func NewDefaultFS() (FS, error) {
return NewFS(DefaultMountPoint)
}
// NewFS returns a new proc FS mounted under the given proc mountPoint. It will error
// if the mount point directory can't be read or is a file.
func NewFS(mountPoint string) (FS, error) { func NewFS(mountPoint string) (FS, error) {
info, err := os.Stat(mountPoint) fs, err := fs.NewFS(mountPoint)
if err != nil { if err != nil {
return "", fmt.Errorf("could not read %s: %s", mountPoint, err) return FS{}, err
} }
if !info.IsDir() { return FS{fs}, nil
return "", fmt.Errorf("mount point %s is not a directory", mountPoint)
}
return FS(mountPoint), nil
}
// Path returns the path of the given subsystem relative to the procfs root.
func (fs FS) Path(p ...string) string {
return path.Join(append([]string{string(fs)}, p...)...)
}
// XFSStats retrieves XFS filesystem runtime statistics.
func (fs FS) XFSStats() (*xfs.Stats, error) {
f, err := os.Open(fs.Path("fs/xfs/stat"))
if err != nil {
return nil, err
}
defer f.Close()
return xfs.ParseStats(f)
} }

3
vendor/github.com/prometheus/procfs/go.mod generated vendored Normal file
View file

@ -0,0 +1,3 @@
module github.com/prometheus/procfs
require golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4

2
vendor/github.com/prometheus/procfs/go.sum generated vendored Normal file
View file

@ -0,0 +1,2 @@
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

52
vendor/github.com/prometheus/procfs/internal/fs/fs.go generated vendored Normal file
View file

@ -0,0 +1,52 @@
// Copyright 2019 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fs
import (
"fmt"
"os"
"path/filepath"
)
const (
// DefaultProcMountPoint is the common mount point of the proc filesystem.
DefaultProcMountPoint = "/proc"
// DefaultSysMountPoint is the common mount point of the sys filesystem.
DefaultSysMountPoint = "/sys"
)
// FS represents a pseudo-filesystem, normally /proc or /sys, which provides an
// interface to kernel data structures.
type FS string
// NewFS returns a new FS mounted under the given mountPoint. It will error
// if the mount point can't be read.
func NewFS(mountPoint string) (FS, error) {
info, err := os.Stat(mountPoint)
if err != nil {
return "", fmt.Errorf("could not read %s: %s", mountPoint, err)
}
if !info.IsDir() {
return "", fmt.Errorf("mount point %s is not a directory", mountPoint)
}
return FS(mountPoint), nil
}
// Path appends the given path elements to the filesystem path, adding separators
// as necessary.
func (fs FS) Path(p ...string) string {
return filepath.Join(append([]string{string(fs)}, p...)...)
}

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 procfs package procfs
import ( import (
@ -31,16 +44,16 @@ type IPVSStats struct {
type IPVSBackendStatus struct { type IPVSBackendStatus struct {
// The local (virtual) IP address. // The local (virtual) IP address.
LocalAddress net.IP LocalAddress net.IP
// The remote (real) IP address.
RemoteAddress net.IP
// The local (virtual) port. // The local (virtual) port.
LocalPort uint16 LocalPort uint16
// The remote (real) port.
RemotePort uint16
// The local firewall mark // The local firewall mark
LocalMark string LocalMark string
// The transport protocol (TCP, UDP). // The transport protocol (TCP, UDP).
Proto string Proto string
// The remote (real) IP address.
RemoteAddress net.IP
// The remote (real) port.
RemotePort uint16
// The current number of active connections for this virtual/real address pair. // The current number of active connections for this virtual/real address pair.
ActiveConn uint64 ActiveConn uint64
// The current number of inactive connections for this virtual/real address pair. // The current number of inactive connections for this virtual/real address pair.
@ -49,19 +62,9 @@ type IPVSBackendStatus struct {
Weight uint64 Weight uint64
} }
// NewIPVSStats reads the IPVS statistics. // IPVSStats reads the IPVS statistics from the specified `proc` filesystem.
func NewIPVSStats() (IPVSStats, error) { func (fs FS) IPVSStats() (IPVSStats, error) {
fs, err := NewFS(DefaultMountPoint) file, err := os.Open(fs.proc.Path("net/ip_vs_stats"))
if err != nil {
return IPVSStats{}, err
}
return fs.NewIPVSStats()
}
// NewIPVSStats reads the IPVS statistics from the specified `proc` filesystem.
func (fs FS) NewIPVSStats() (IPVSStats, error) {
file, err := os.Open(fs.Path("net/ip_vs_stats"))
if err != nil { if err != nil {
return IPVSStats{}, err return IPVSStats{}, err
} }
@ -118,19 +121,9 @@ func parseIPVSStats(file io.Reader) (IPVSStats, error) {
return stats, nil return stats, nil
} }
// NewIPVSBackendStatus reads and returns the status of all (virtual,real) server pairs. // IPVSBackendStatus reads and returns the status of all (virtual,real) server pairs from the specified `proc` filesystem.
func NewIPVSBackendStatus() ([]IPVSBackendStatus, error) { func (fs FS) IPVSBackendStatus() ([]IPVSBackendStatus, error) {
fs, err := NewFS(DefaultMountPoint) file, err := os.Open(fs.proc.Path("net/ip_vs"))
if err != nil {
return []IPVSBackendStatus{}, err
}
return fs.NewIPVSBackendStatus()
}
// NewIPVSBackendStatus reads and returns the status of all (virtual,real) server pairs from the specified `proc` filesystem.
func (fs FS) NewIPVSBackendStatus() ([]IPVSBackendStatus, error) {
file, err := os.Open(fs.Path("net/ip_vs"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -151,7 +144,7 @@ func parseIPVSBackendStatus(file io.Reader) ([]IPVSBackendStatus, error) {
) )
for scanner.Scan() { for scanner.Scan() {
fields := strings.Fields(string(scanner.Text())) fields := strings.Fields(scanner.Text())
if len(fields) == 0 { if len(fields) == 0 {
continue continue
} }

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 procfs package procfs
import ( import (
@ -29,64 +42,64 @@ type MDStat struct {
BlocksSynced int64 BlocksSynced int64
} }
// ParseMDStat parses an mdstat-file and returns a struct with the relevant infos. // MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of
func (fs FS) ParseMDStat() (mdstates []MDStat, err error) { // structs containing the relevant info. More information available here:
mdStatusFilePath := fs.Path("mdstat") // https://raid.wiki.kernel.org/index.php/Mdstat
content, err := ioutil.ReadFile(mdStatusFilePath) func (fs FS) MDStat() ([]MDStat, error) {
data, err := ioutil.ReadFile(fs.proc.Path("mdstat"))
if err != nil { if err != nil {
return []MDStat{}, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err) return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err)
} }
mdstat, err := parseMDStat(data)
if err != nil {
return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err)
}
return mdstat, nil
}
mdStates := []MDStat{} // parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of
lines := strings.Split(string(content), "\n") // structs containing the relevant info.
func parseMDStat(mdstatData []byte) ([]MDStat, error) {
mdStats := []MDStat{}
lines := strings.Split(string(mdstatData), "\n")
for i, l := range lines { for i, l := range lines {
if l == "" { if strings.TrimSpace(l) == "" || l[0] == ' ' ||
continue strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") {
}
if l[0] == ' ' {
continue
}
if strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") {
continue continue
} }
mainLine := strings.Split(l, " ") deviceFields := strings.Fields(l)
if len(mainLine) < 3 { if len(deviceFields) < 3 {
return mdStates, fmt.Errorf("error parsing mdline: %s", l) return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", l)
} }
mdName := mainLine[0] mdName := deviceFields[0]
activityState := mainLine[2] activityState := deviceFields[2]
if len(lines) <= i+3 { if len(lines) <= i+3 {
return mdStates, fmt.Errorf( return mdStats, fmt.Errorf("missing lines for md device %s", mdName)
"error parsing %s: too few lines for md device %s",
mdStatusFilePath,
mdName,
)
} }
active, total, size, err := evalStatusline(lines[i+1]) active, total, size, err := evalStatusLine(lines[i+1])
if err != nil { if err != nil {
return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err) return nil, err
} }
// j is the line number of the syncing-line. syncLineIdx := i + 2
j := i + 2
if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line
j = i + 3 syncLineIdx++
} }
// If device is syncing at the moment, get the number of currently // If device is recovering/syncing at the moment, get the number of currently
// synced bytes, otherwise that number equals the size of the device. // synced bytes, otherwise that number equals the size of the device.
syncedBlocks := size syncedBlocks := size
if strings.Contains(lines[j], "recovery") || strings.Contains(lines[j], "resync") { if strings.Contains(lines[syncLineIdx], "recovery") || strings.Contains(lines[syncLineIdx], "resync") {
syncedBlocks, err = evalBuildline(lines[j]) syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx])
if err != nil { if err != nil {
return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err) return nil, err
} }
} }
mdStates = append(mdStates, MDStat{ mdStats = append(mdStats, MDStat{
Name: mdName, Name: mdName,
ActivityState: activityState, ActivityState: activityState,
DisksActive: active, DisksActive: active,
@ -96,10 +109,10 @@ func (fs FS) ParseMDStat() (mdstates []MDStat, err error) {
}) })
} }
return mdStates, nil return mdStats, nil
} }
func evalStatusline(statusline string) (active, total, size int64, err error) { func evalStatusLine(statusline string) (active, total, size int64, err error) {
matches := statuslineRE.FindStringSubmatch(statusline) matches := statuslineRE.FindStringSubmatch(statusline)
if len(matches) != 4 { if len(matches) != 4 {
return 0, 0, 0, fmt.Errorf("unexpected statusline: %s", statusline) return 0, 0, 0, fmt.Errorf("unexpected statusline: %s", statusline)
@ -123,7 +136,7 @@ func evalStatusline(statusline string) (active, total, size int64, err error) {
return active, total, size, nil return active, total, size, nil
} }
func evalBuildline(buildline string) (syncedBlocks int64, err error) { func evalRecoveryLine(buildline string) (syncedBlocks int64, err error) {
matches := buildlineRE.FindStringSubmatch(buildline) matches := buildlineRE.FindStringSubmatch(buildline)
if len(matches) != 2 { if len(matches) != 2 {
return 0, fmt.Errorf("unexpected buildline: %s", buildline) return 0, fmt.Errorf("unexpected buildline: %s", buildline)

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 procfs package procfs
// While implementing parsing of /proc/[pid]/mountstats, this blog was used // While implementing parsing of /proc/[pid]/mountstats, this blog was used
@ -26,8 +39,11 @@ const (
statVersion10 = "1.0" statVersion10 = "1.0"
statVersion11 = "1.1" statVersion11 = "1.1"
fieldTransport10Len = 10 fieldTransport10TCPLen = 10
fieldTransport11Len = 13 fieldTransport10UDPLen = 7
fieldTransport11TCPLen = 13
fieldTransport11UDPLen = 10
) )
// A Mount is a device mount parsed from /proc/[pid]/mountstats. // A Mount is a device mount parsed from /proc/[pid]/mountstats.
@ -53,6 +69,8 @@ type MountStats interface {
type MountStatsNFS struct { type MountStatsNFS struct {
// The version of statistics provided. // The version of statistics provided.
StatVersion string StatVersion string
// The mount options of the NFS mount.
Opts map[string]string
// The age of the NFS mount. // The age of the NFS mount.
Age time.Duration Age time.Duration
// Statistics related to byte counters for various operations. // Statistics related to byte counters for various operations.
@ -163,16 +181,18 @@ type NFSOperationStats struct {
// Number of bytes received for this operation, including RPC headers and payload. // Number of bytes received for this operation, including RPC headers and payload.
BytesReceived uint64 BytesReceived uint64
// Duration all requests spent queued for transmission before they were sent. // Duration all requests spent queued for transmission before they were sent.
CumulativeQueueTime time.Duration CumulativeQueueMilliseconds uint64
// Duration it took to get a reply back after the request was transmitted. // Duration it took to get a reply back after the request was transmitted.
CumulativeTotalResponseTime time.Duration CumulativeTotalResponseMilliseconds uint64
// Duration from when a request was enqueued to when it was completely handled. // Duration from when a request was enqueued to when it was completely handled.
CumulativeTotalRequestTime time.Duration CumulativeTotalRequestMilliseconds uint64
} }
// A NFSTransportStats contains statistics for the NFS mount RPC requests and // A NFSTransportStats contains statistics for the NFS mount RPC requests and
// responses. // responses.
type NFSTransportStats struct { type NFSTransportStats struct {
// The transport protocol used for the NFS mount.
Protocol string
// The local port used for the NFS mount. // The local port used for the NFS mount.
Port uint64 Port uint64
// Number of times the client has had to establish a connection from scratch // Number of times the client has had to establish a connection from scratch
@ -184,7 +204,7 @@ type NFSTransportStats struct {
// spent waiting for connections to the server to be established. // spent waiting for connections to the server to be established.
ConnectIdleTime uint64 ConnectIdleTime uint64
// Duration since the NFS mount last saw any RPC traffic. // Duration since the NFS mount last saw any RPC traffic.
IdleTime time.Duration IdleTimeSeconds uint64
// Number of RPC requests for this mount sent to the NFS server. // Number of RPC requests for this mount sent to the NFS server.
Sends uint64 Sends uint64
// Number of RPC responses for this mount received from the NFS server. // Number of RPC responses for this mount received from the NFS server.
@ -299,6 +319,7 @@ func parseMount(ss []string) (*Mount, error) {
func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) { func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
// Field indicators for parsing specific types of data // Field indicators for parsing specific types of data
const ( const (
fieldOpts = "opts:"
fieldAge = "age:" fieldAge = "age:"
fieldBytes = "bytes:" fieldBytes = "bytes:"
fieldEvents = "events:" fieldEvents = "events:"
@ -320,6 +341,18 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
} }
switch ss[0] { switch ss[0] {
case fieldOpts:
if stats.Opts == nil {
stats.Opts = map[string]string{}
}
for _, opt := range strings.Split(ss[1], ",") {
split := strings.Split(opt, "=")
if len(split) == 2 {
stats.Opts[split[0]] = split[1]
} else {
stats.Opts[opt] = ""
}
}
case fieldAge: case fieldAge:
// Age integer is in seconds // Age integer is in seconds
d, err := time.ParseDuration(ss[1] + "s") d, err := time.ParseDuration(ss[1] + "s")
@ -347,7 +380,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss) return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss)
} }
tstats, err := parseNFSTransportStats(ss[2:], statVersion) tstats, err := parseNFSTransportStats(ss[1:], statVersion)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -491,15 +524,15 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
} }
ops = append(ops, NFSOperationStats{ ops = append(ops, NFSOperationStats{
Operation: strings.TrimSuffix(ss[0], ":"), Operation: strings.TrimSuffix(ss[0], ":"),
Requests: ns[0], Requests: ns[0],
Transmissions: ns[1], Transmissions: ns[1],
MajorTimeouts: ns[2], MajorTimeouts: ns[2],
BytesSent: ns[3], BytesSent: ns[3],
BytesReceived: ns[4], BytesReceived: ns[4],
CumulativeQueueTime: time.Duration(ns[5]) * time.Millisecond, CumulativeQueueMilliseconds: ns[5],
CumulativeTotalResponseTime: time.Duration(ns[6]) * time.Millisecond, CumulativeTotalResponseMilliseconds: ns[6],
CumulativeTotalRequestTime: time.Duration(ns[7]) * time.Millisecond, CumulativeTotalRequestMilliseconds: ns[7],
}) })
} }
@ -509,13 +542,33 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
// parseNFSTransportStats parses a NFSTransportStats line using an input set of // parseNFSTransportStats parses a NFSTransportStats line using an input set of
// integer fields matched to a specific stats version. // integer fields matched to a specific stats version.
func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) { func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
// Extract the protocol field. It is the only string value in the line
protocol := ss[0]
ss = ss[1:]
switch statVersion { switch statVersion {
case statVersion10: case statVersion10:
if len(ss) != fieldTransport10Len { var expectedLength int
if protocol == "tcp" {
expectedLength = fieldTransport10TCPLen
} else if protocol == "udp" {
expectedLength = fieldTransport10UDPLen
} else {
return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.0 statement: %v", protocol, ss)
}
if len(ss) != expectedLength {
return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss) return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss)
} }
case statVersion11: case statVersion11:
if len(ss) != fieldTransport11Len { var expectedLength int
if protocol == "tcp" {
expectedLength = fieldTransport11TCPLen
} else if protocol == "udp" {
expectedLength = fieldTransport11UDPLen
} else {
return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.1 statement: %v", protocol, ss)
}
if len(ss) != expectedLength {
return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss) return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss)
} }
default: default:
@ -523,12 +576,13 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
} }
// Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay // Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
// in a v1.0 response. // in a v1.0 response. Since the stat length is bigger for TCP stats, we use
// the TCP length here.
// //
// Note: slice length must be set to length of v1.1 stats to avoid a panic when // Note: slice length must be set to length of v1.1 stats to avoid a panic when
// only v1.0 stats are present. // only v1.0 stats are present.
// See: https://github.com/prometheus/node_exporter/issues/571. // See: https://github.com/prometheus/node_exporter/issues/571.
ns := make([]uint64, fieldTransport11Len) ns := make([]uint64, fieldTransport11TCPLen)
for i, s := range ss { for i, s := range ss {
n, err := strconv.ParseUint(s, 10, 64) n, err := strconv.ParseUint(s, 10, 64)
if err != nil { if err != nil {
@ -538,12 +592,23 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
ns[i] = n ns[i] = n
} }
// The fields differ depending on the transport protocol (TCP or UDP)
// From https://utcc.utoronto.ca/%7Ecks/space/blog/linux/NFSMountstatsXprt
//
// For the udp RPC transport there is no connection count, connect idle time,
// or idle time (fields #3, #4, and #5); all other fields are the same. So
// we set them to 0 here.
if protocol == "udp" {
ns = append(ns[:2], append(make([]uint64, 3), ns[2:]...)...)
}
return &NFSTransportStats{ return &NFSTransportStats{
Protocol: protocol,
Port: ns[0], Port: ns[0],
Bind: ns[1], Bind: ns[1],
Connect: ns[2], Connect: ns[2],
ConnectIdleTime: ns[3], ConnectIdleTime: ns[3],
IdleTime: time.Duration(ns[4]) * time.Second, IdleTimeSeconds: ns[4],
Sends: ns[5], Sends: ns[5],
Receives: ns[6], Receives: ns[6],
BadTransactionIDs: ns[7], BadTransactionIDs: ns[7],

206
vendor/github.com/prometheus/procfs/net_dev.go generated vendored Normal file
View file

@ -0,0 +1,206 @@
// 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 procfs
import (
"bufio"
"errors"
"os"
"sort"
"strconv"
"strings"
)
// NetDevLine is single line parsed from /proc/net/dev or /proc/[pid]/net/dev.
type NetDevLine struct {
Name string `json:"name"` // The name of the interface.
RxBytes uint64 `json:"rx_bytes"` // Cumulative count of bytes received.
RxPackets uint64 `json:"rx_packets"` // Cumulative count of packets received.
RxErrors uint64 `json:"rx_errors"` // Cumulative count of receive errors encountered.
RxDropped uint64 `json:"rx_dropped"` // Cumulative count of packets dropped while receiving.
RxFIFO uint64 `json:"rx_fifo"` // Cumulative count of FIFO buffer errors.
RxFrame uint64 `json:"rx_frame"` // Cumulative count of packet framing errors.
RxCompressed uint64 `json:"rx_compressed"` // Cumulative count of compressed packets received by the device driver.
RxMulticast uint64 `json:"rx_multicast"` // Cumulative count of multicast frames received by the device driver.
TxBytes uint64 `json:"tx_bytes"` // Cumulative count of bytes transmitted.
TxPackets uint64 `json:"tx_packets"` // Cumulative count of packets transmitted.
TxErrors uint64 `json:"tx_errors"` // Cumulative count of transmit errors encountered.
TxDropped uint64 `json:"tx_dropped"` // Cumulative count of packets dropped while transmitting.
TxFIFO uint64 `json:"tx_fifo"` // Cumulative count of FIFO buffer errors.
TxCollisions uint64 `json:"tx_collisions"` // Cumulative count of collisions detected on the interface.
TxCarrier uint64 `json:"tx_carrier"` // Cumulative count of carrier losses detected by the device driver.
TxCompressed uint64 `json:"tx_compressed"` // Cumulative count of compressed packets transmitted by the device driver.
}
// NetDev is parsed from /proc/net/dev or /proc/[pid]/net/dev. The map keys
// are interface names.
type NetDev map[string]NetDevLine
// NetDev returns kernel/system statistics read from /proc/net/dev.
func (fs FS) NetDev() (NetDev, error) {
return newNetDev(fs.proc.Path("net/dev"))
}
// NetDev returns kernel/system statistics read from /proc/[pid]/net/dev.
func (p Proc) NetDev() (NetDev, error) {
return newNetDev(p.path("net/dev"))
}
// newNetDev creates a new NetDev from the contents of the given file.
func newNetDev(file string) (NetDev, error) {
f, err := os.Open(file)
if err != nil {
return NetDev{}, err
}
defer f.Close()
netDev := NetDev{}
s := bufio.NewScanner(f)
for n := 0; s.Scan(); n++ {
// Skip the 2 header lines.
if n < 2 {
continue
}
line, err := netDev.parseLine(s.Text())
if err != nil {
return netDev, err
}
netDev[line.Name] = *line
}
return netDev, s.Err()
}
// parseLine parses a single line from the /proc/net/dev file. Header lines
// must be filtered prior to calling this method.
func (netDev NetDev) parseLine(rawLine string) (*NetDevLine, error) {
parts := strings.SplitN(rawLine, ":", 2)
if len(parts) != 2 {
return nil, errors.New("invalid net/dev line, missing colon")
}
fields := strings.Fields(strings.TrimSpace(parts[1]))
var err error
line := &NetDevLine{}
// Interface Name
line.Name = strings.TrimSpace(parts[0])
if line.Name == "" {
return nil, errors.New("invalid net/dev line, empty interface name")
}
// RX
line.RxBytes, err = strconv.ParseUint(fields[0], 10, 64)
if err != nil {
return nil, err
}
line.RxPackets, err = strconv.ParseUint(fields[1], 10, 64)
if err != nil {
return nil, err
}
line.RxErrors, err = strconv.ParseUint(fields[2], 10, 64)
if err != nil {
return nil, err
}
line.RxDropped, err = strconv.ParseUint(fields[3], 10, 64)
if err != nil {
return nil, err
}
line.RxFIFO, err = strconv.ParseUint(fields[4], 10, 64)
if err != nil {
return nil, err
}
line.RxFrame, err = strconv.ParseUint(fields[5], 10, 64)
if err != nil {
return nil, err
}
line.RxCompressed, err = strconv.ParseUint(fields[6], 10, 64)
if err != nil {
return nil, err
}
line.RxMulticast, err = strconv.ParseUint(fields[7], 10, 64)
if err != nil {
return nil, err
}
// TX
line.TxBytes, err = strconv.ParseUint(fields[8], 10, 64)
if err != nil {
return nil, err
}
line.TxPackets, err = strconv.ParseUint(fields[9], 10, 64)
if err != nil {
return nil, err
}
line.TxErrors, err = strconv.ParseUint(fields[10], 10, 64)
if err != nil {
return nil, err
}
line.TxDropped, err = strconv.ParseUint(fields[11], 10, 64)
if err != nil {
return nil, err
}
line.TxFIFO, err = strconv.ParseUint(fields[12], 10, 64)
if err != nil {
return nil, err
}
line.TxCollisions, err = strconv.ParseUint(fields[13], 10, 64)
if err != nil {
return nil, err
}
line.TxCarrier, err = strconv.ParseUint(fields[14], 10, 64)
if err != nil {
return nil, err
}
line.TxCompressed, err = strconv.ParseUint(fields[15], 10, 64)
if err != nil {
return nil, err
}
return line, nil
}
// Total aggregates the values across interfaces and returns a new NetDevLine.
// The Name field will be a sorted comma separated list of interface names.
func (netDev NetDev) Total() NetDevLine {
total := NetDevLine{}
names := make([]string, 0, len(netDev))
for _, ifc := range netDev {
names = append(names, ifc.Name)
total.RxBytes += ifc.RxBytes
total.RxPackets += ifc.RxPackets
total.RxPackets += ifc.RxPackets
total.RxErrors += ifc.RxErrors
total.RxDropped += ifc.RxDropped
total.RxFIFO += ifc.RxFIFO
total.RxFrame += ifc.RxFrame
total.RxCompressed += ifc.RxCompressed
total.RxMulticast += ifc.RxMulticast
total.TxBytes += ifc.TxBytes
total.TxPackets += ifc.TxPackets
total.TxErrors += ifc.TxErrors
total.TxDropped += ifc.TxDropped
total.TxFIFO += ifc.TxFIFO
total.TxCollisions += ifc.TxCollisions
total.TxCarrier += ifc.TxCarrier
total.TxCompressed += ifc.TxCompressed
}
sort.Strings(names)
total.Name = strings.Join(names, ", ")
return total
}

275
vendor/github.com/prometheus/procfs/net_unix.go generated vendored Normal file
View file

@ -0,0 +1,275 @@
// 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 procfs
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"strconv"
"strings"
)
// For the proc file format details,
// see https://elixir.bootlin.com/linux/v4.17/source/net/unix/af_unix.c#L2815
// and https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/net.h#L48.
const (
netUnixKernelPtrIdx = iota
netUnixRefCountIdx
_
netUnixFlagsIdx
netUnixTypeIdx
netUnixStateIdx
netUnixInodeIdx
// Inode and Path are optional.
netUnixStaticFieldsCnt = 6
)
const (
netUnixTypeStream = 1
netUnixTypeDgram = 2
netUnixTypeSeqpacket = 5
netUnixFlagListen = 1 << 16
netUnixStateUnconnected = 1
netUnixStateConnecting = 2
netUnixStateConnected = 3
netUnixStateDisconnected = 4
)
var errInvalidKernelPtrFmt = errors.New("Invalid Num(the kernel table slot number) format")
// NetUnixType is the type of the type field.
type NetUnixType uint64
// NetUnixFlags is the type of the flags field.
type NetUnixFlags uint64
// NetUnixState is the type of the state field.
type NetUnixState uint64
// NetUnixLine represents a line of /proc/net/unix.
type NetUnixLine struct {
KernelPtr string
RefCount uint64
Protocol uint64
Flags NetUnixFlags
Type NetUnixType
State NetUnixState
Inode uint64
Path string
}
// NetUnix holds the data read from /proc/net/unix.
type NetUnix struct {
Rows []*NetUnixLine
}
// NewNetUnix returns data read from /proc/net/unix.
func NewNetUnix() (*NetUnix, error) {
fs, err := NewFS(DefaultMountPoint)
if err != nil {
return nil, err
}
return fs.NewNetUnix()
}
// NewNetUnix returns data read from /proc/net/unix.
func (fs FS) NewNetUnix() (*NetUnix, error) {
return NewNetUnixByPath(fs.proc.Path("net/unix"))
}
// NewNetUnixByPath returns data read from /proc/net/unix by file path.
// It might returns an error with partial parsed data, if an error occur after some data parsed.
func NewNetUnixByPath(path string) (*NetUnix, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return NewNetUnixByReader(f)
}
// NewNetUnixByReader returns data read from /proc/net/unix by a reader.
// It might returns an error with partial parsed data, if an error occur after some data parsed.
func NewNetUnixByReader(reader io.Reader) (*NetUnix, error) {
nu := &NetUnix{
Rows: make([]*NetUnixLine, 0, 32),
}
scanner := bufio.NewScanner(reader)
// Omit the header line.
scanner.Scan()
header := scanner.Text()
// From the man page of proc(5), it does not contain an Inode field,
// but in actually it exists.
// This code works for both cases.
hasInode := strings.Contains(header, "Inode")
minFieldsCnt := netUnixStaticFieldsCnt
if hasInode {
minFieldsCnt++
}
for scanner.Scan() {
line := scanner.Text()
item, err := nu.parseLine(line, hasInode, minFieldsCnt)
if err != nil {
return nu, err
}
nu.Rows = append(nu.Rows, item)
}
return nu, scanner.Err()
}
func (u *NetUnix) parseLine(line string, hasInode bool, minFieldsCnt int) (*NetUnixLine, error) {
fields := strings.Fields(line)
fieldsLen := len(fields)
if fieldsLen < minFieldsCnt {
return nil, fmt.Errorf(
"Parse Unix domain failed: expect at least %d fields but got %d",
minFieldsCnt, fieldsLen)
}
kernelPtr, err := u.parseKernelPtr(fields[netUnixKernelPtrIdx])
if err != nil {
return nil, fmt.Errorf("Parse Unix domain num(%s) failed: %s", fields[netUnixKernelPtrIdx], err)
}
users, err := u.parseUsers(fields[netUnixRefCountIdx])
if err != nil {
return nil, fmt.Errorf("Parse Unix domain ref count(%s) failed: %s", fields[netUnixRefCountIdx], err)
}
flags, err := u.parseFlags(fields[netUnixFlagsIdx])
if err != nil {
return nil, fmt.Errorf("Parse Unix domain flags(%s) failed: %s", fields[netUnixFlagsIdx], err)
}
typ, err := u.parseType(fields[netUnixTypeIdx])
if err != nil {
return nil, fmt.Errorf("Parse Unix domain type(%s) failed: %s", fields[netUnixTypeIdx], err)
}
state, err := u.parseState(fields[netUnixStateIdx])
if err != nil {
return nil, fmt.Errorf("Parse Unix domain state(%s) failed: %s", fields[netUnixStateIdx], err)
}
var inode uint64
if hasInode {
inodeStr := fields[netUnixInodeIdx]
inode, err = u.parseInode(inodeStr)
if err != nil {
return nil, fmt.Errorf("Parse Unix domain inode(%s) failed: %s", inodeStr, err)
}
}
nuLine := &NetUnixLine{
KernelPtr: kernelPtr,
RefCount: users,
Type: typ,
Flags: flags,
State: state,
Inode: inode,
}
// Path field is optional.
if fieldsLen > minFieldsCnt {
pathIdx := netUnixInodeIdx + 1
if !hasInode {
pathIdx--
}
nuLine.Path = fields[pathIdx]
}
return nuLine, nil
}
func (u NetUnix) parseKernelPtr(str string) (string, error) {
if !strings.HasSuffix(str, ":") {
return "", errInvalidKernelPtrFmt
}
return str[:len(str)-1], nil
}
func (u NetUnix) parseUsers(hexStr string) (uint64, error) {
return strconv.ParseUint(hexStr, 16, 32)
}
func (u NetUnix) parseProtocol(hexStr string) (uint64, error) {
return strconv.ParseUint(hexStr, 16, 32)
}
func (u NetUnix) parseType(hexStr string) (NetUnixType, error) {
typ, err := strconv.ParseUint(hexStr, 16, 16)
if err != nil {
return 0, err
}
return NetUnixType(typ), nil
}
func (u NetUnix) parseFlags(hexStr string) (NetUnixFlags, error) {
flags, err := strconv.ParseUint(hexStr, 16, 32)
if err != nil {
return 0, err
}
return NetUnixFlags(flags), nil
}
func (u NetUnix) parseState(hexStr string) (NetUnixState, error) {
st, err := strconv.ParseInt(hexStr, 16, 8)
if err != nil {
return 0, err
}
return NetUnixState(st), nil
}
func (u NetUnix) parseInode(inodeStr string) (uint64, error) {
return strconv.ParseUint(inodeStr, 10, 64)
}
func (t NetUnixType) String() string {
switch t {
case netUnixTypeStream:
return "stream"
case netUnixTypeDgram:
return "dgram"
case netUnixTypeSeqpacket:
return "seqpacket"
}
return "unknown"
}
func (f NetUnixFlags) String() string {
switch f {
case netUnixFlagListen:
return "listen"
default:
return "default"
}
}
func (s NetUnixState) String() string {
switch s {
case netUnixStateUnconnected:
return "unconnected"
case netUnixStateConnecting:
return "connecting"
case netUnixStateConnected:
return "connected"
case netUnixStateDisconnected:
return "disconnected"
}
return "unknown"
}

View file

@ -1,11 +1,27 @@
// 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 procfs package procfs
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"github.com/prometheus/procfs/internal/fs"
) )
// Proc provides information about a running process. // Proc provides information about a running process.
@ -13,7 +29,7 @@ type Proc struct {
// The process ID. // The process ID.
PID int PID int
fs FS fs fs.FS
} }
// Procs represents a list of Proc structs. // Procs represents a list of Proc structs.
@ -38,7 +54,7 @@ func NewProc(pid int) (Proc, error) {
if err != nil { if err != nil {
return Proc{}, err return Proc{}, err
} }
return fs.NewProc(pid) return fs.Proc(pid)
} }
// AllProcs returns a list of all currently available processes under /proc. // AllProcs returns a list of all currently available processes under /proc.
@ -52,28 +68,35 @@ func AllProcs() (Procs, error) {
// Self returns a process for the current process. // Self returns a process for the current process.
func (fs FS) Self() (Proc, error) { func (fs FS) Self() (Proc, error) {
p, err := os.Readlink(fs.Path("self")) p, err := os.Readlink(fs.proc.Path("self"))
if err != nil { if err != nil {
return Proc{}, err return Proc{}, err
} }
pid, err := strconv.Atoi(strings.Replace(p, string(fs), "", -1)) pid, err := strconv.Atoi(strings.Replace(p, string(fs.proc), "", -1))
if err != nil { if err != nil {
return Proc{}, err return Proc{}, err
} }
return fs.NewProc(pid) return fs.Proc(pid)
} }
// NewProc returns a process for the given pid. // NewProc returns a process for the given pid.
//
// Deprecated: use fs.Proc() instead
func (fs FS) NewProc(pid int) (Proc, error) { func (fs FS) NewProc(pid int) (Proc, error) {
if _, err := os.Stat(fs.Path(strconv.Itoa(pid))); err != nil { return fs.Proc(pid)
}
// Proc returns a process for the given pid.
func (fs FS) Proc(pid int) (Proc, error) {
if _, err := os.Stat(fs.proc.Path(strconv.Itoa(pid))); err != nil {
return Proc{}, err return Proc{}, err
} }
return Proc{PID: pid, fs: fs}, nil return Proc{PID: pid, fs: fs.proc}, nil
} }
// AllProcs returns a list of all currently available processes. // AllProcs returns a list of all currently available processes.
func (fs FS) AllProcs() (Procs, error) { func (fs FS) AllProcs() (Procs, error) {
d, err := os.Open(fs.Path()) d, err := os.Open(fs.proc.Path())
if err != nil { if err != nil {
return Procs{}, err return Procs{}, err
} }
@ -90,7 +113,7 @@ func (fs FS) AllProcs() (Procs, error) {
if err != nil { if err != nil {
continue continue
} }
p = append(p, Proc{PID: int(pid), fs: fs}) p = append(p, Proc{PID: int(pid), fs: fs.proc})
} }
return p, nil return p, nil
@ -113,7 +136,7 @@ func (p Proc) CmdLine() ([]string, error) {
return []string{}, nil return []string{}, nil
} }
return strings.Split(string(data[:len(data)-1]), string(byte(0))), nil return strings.Split(string(bytes.TrimRight(data, string("\x00"))), string(byte(0))), nil
} }
// Comm returns the command name of a process. // Comm returns the command name of a process.
@ -142,6 +165,26 @@ func (p Proc) Executable() (string, error) {
return exe, err return exe, err
} }
// Cwd returns the absolute path to the current working directory of the process.
func (p Proc) Cwd() (string, error) {
wd, err := os.Readlink(p.path("cwd"))
if os.IsNotExist(err) {
return "", nil
}
return wd, err
}
// RootDir returns the absolute path to the process's root directory (as set by chroot)
func (p Proc) RootDir() (string, error) {
rdir, err := os.Readlink(p.path("root"))
if os.IsNotExist(err) {
return "", nil
}
return rdir, err
}
// FileDescriptors returns the currently open file descriptors of a process. // FileDescriptors returns the currently open file descriptors of a process.
func (p Proc) FileDescriptors() ([]uintptr, error) { func (p Proc) FileDescriptors() ([]uintptr, error) {
names, err := p.fileDescriptors() names, err := p.fileDescriptors()

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 procfs package procfs
import ( import (
@ -26,8 +39,8 @@ type ProcIO struct {
CancelledWriteBytes int64 CancelledWriteBytes int64
} }
// NewIO creates a new ProcIO instance from a given Proc instance. // IO creates a new ProcIO instance from a given Proc instance.
func (p Proc) NewIO() (ProcIO, error) { func (p Proc) IO() (ProcIO, error) {
pio := ProcIO{} pio := ProcIO{}
f, err := os.Open(p.path("io")) f, err := os.Open(p.path("io"))
@ -47,9 +60,6 @@ func (p Proc) NewIO() (ProcIO, error) {
_, err = fmt.Sscanf(string(data), ioFormat, &pio.RChar, &pio.WChar, &pio.SyscR, _, err = fmt.Sscanf(string(data), ioFormat, &pio.RChar, &pio.WChar, &pio.SyscR,
&pio.SyscW, &pio.ReadBytes, &pio.WriteBytes, &pio.CancelledWriteBytes) &pio.SyscW, &pio.ReadBytes, &pio.WriteBytes, &pio.CancelledWriteBytes)
if err != nil {
return pio, err
}
return pio, nil return pio, err
} }

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 procfs package procfs
import ( import (
@ -13,46 +26,46 @@ import (
// http://man7.org/linux/man-pages/man2/getrlimit.2.html. // http://man7.org/linux/man-pages/man2/getrlimit.2.html.
type ProcLimits struct { type ProcLimits struct {
// CPU time limit in seconds. // CPU time limit in seconds.
CPUTime int CPUTime int64
// Maximum size of files that the process may create. // Maximum size of files that the process may create.
FileSize int FileSize int64
// Maximum size of the process's data segment (initialized data, // Maximum size of the process's data segment (initialized data,
// uninitialized data, and heap). // uninitialized data, and heap).
DataSize int DataSize int64
// Maximum size of the process stack in bytes. // Maximum size of the process stack in bytes.
StackSize int StackSize int64
// Maximum size of a core file. // Maximum size of a core file.
CoreFileSize int CoreFileSize int64
// Limit of the process's resident set in pages. // Limit of the process's resident set in pages.
ResidentSet int ResidentSet int64
// Maximum number of processes that can be created for the real user ID of // Maximum number of processes that can be created for the real user ID of
// the calling process. // the calling process.
Processes int Processes int64
// Value one greater than the maximum file descriptor number that can be // Value one greater than the maximum file descriptor number that can be
// opened by this process. // opened by this process.
OpenFiles int OpenFiles int64
// Maximum number of bytes of memory that may be locked into RAM. // Maximum number of bytes of memory that may be locked into RAM.
LockedMemory int LockedMemory int64
// Maximum size of the process's virtual memory address space in bytes. // Maximum size of the process's virtual memory address space in bytes.
AddressSpace int AddressSpace int64
// Limit on the combined number of flock(2) locks and fcntl(2) leases that // Limit on the combined number of flock(2) locks and fcntl(2) leases that
// this process may establish. // this process may establish.
FileLocks int FileLocks int64
// Limit of signals that may be queued for the real user ID of the calling // Limit of signals that may be queued for the real user ID of the calling
// process. // process.
PendingSignals int PendingSignals int64
// Limit on the number of bytes that can be allocated for POSIX message // Limit on the number of bytes that can be allocated for POSIX message
// queues for the real user ID of the calling process. // queues for the real user ID of the calling process.
MsqqueueSize int MsqqueueSize int64
// Limit of the nice priority set using setpriority(2) or nice(2). // Limit of the nice priority set using setpriority(2) or nice(2).
NicePriority int NicePriority int64
// Limit of the real-time priority set using sched_setscheduler(2) or // Limit of the real-time priority set using sched_setscheduler(2) or
// sched_setparam(2). // sched_setparam(2).
RealtimePriority int RealtimePriority int64
// Limit (in microseconds) on the amount of CPU time that a process // Limit (in microseconds) on the amount of CPU time that a process
// scheduled under a real-time scheduling policy may consume without making // scheduled under a real-time scheduling policy may consume without making
// a blocking system call. // a blocking system call.
RealtimeTimeout int RealtimeTimeout int64
} }
const ( const (
@ -65,7 +78,14 @@ var (
) )
// NewLimits returns the current soft limits of the process. // NewLimits returns the current soft limits of the process.
//
// Deprecated: use p.Limits() instead
func (p Proc) NewLimits() (ProcLimits, error) { func (p Proc) NewLimits() (ProcLimits, error) {
return p.Limits()
}
// Limits returns the current soft limits of the process.
func (p Proc) Limits() (ProcLimits, error) {
f, err := os.Open(p.path("limits")) f, err := os.Open(p.path("limits"))
if err != nil { if err != nil {
return ProcLimits{}, err return ProcLimits{}, err
@ -125,13 +145,13 @@ func (p Proc) NewLimits() (ProcLimits, error) {
return l, s.Err() return l, s.Err()
} }
func parseInt(s string) (int, error) { func parseInt(s string) (int64, error) {
if s == limitsUnlimited { if s == limitsUnlimited {
return -1, nil return -1, nil
} }
i, err := strconv.ParseInt(s, 10, 32) i, err := strconv.ParseInt(s, 10, 64)
if err != nil { if err != nil {
return 0, fmt.Errorf("couldn't parse value %s: %s", s, err) return 0, fmt.Errorf("couldn't parse value %s: %s", s, err)
} }
return int(i), nil return i, nil
} }

68
vendor/github.com/prometheus/procfs/proc_ns.go generated vendored Normal file
View file

@ -0,0 +1,68 @@
// 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 procfs
import (
"fmt"
"os"
"strconv"
"strings"
)
// Namespace represents a single namespace of a process.
type Namespace struct {
Type string // Namespace type.
Inode uint32 // Inode number of the namespace. If two processes are in the same namespace their inodes will match.
}
// Namespaces contains all of the namespaces that the process is contained in.
type Namespaces map[string]Namespace
// Namespaces reads from /proc/<pid>/ns/* to get the namespaces of which the
// process is a member.
func (p Proc) Namespaces() (Namespaces, error) {
d, err := os.Open(p.path("ns"))
if err != nil {
return nil, err
}
defer d.Close()
names, err := d.Readdirnames(-1)
if err != nil {
return nil, fmt.Errorf("failed to read contents of ns dir: %v", err)
}
ns := make(Namespaces, len(names))
for _, name := range names {
target, err := os.Readlink(p.path("ns", name))
if err != nil {
return nil, err
}
fields := strings.SplitN(target, ":", 2)
if len(fields) != 2 {
return nil, fmt.Errorf("failed to parse namespace type and inode from '%v'", target)
}
typ := fields[0]
inode, err := strconv.ParseUint(strings.Trim(fields[1], "[]"), 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse inode from '%v': %v", fields[1], err)
}
ns[name] = Namespace{typ, uint32(inode)}
}
return ns, nil
}

101
vendor/github.com/prometheus/procfs/proc_psi.go generated vendored Normal file
View file

@ -0,0 +1,101 @@
// Copyright 2019 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package procfs
// The PSI / pressure interface is described at
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/accounting/psi.txt
// Each resource (cpu, io, memory, ...) is exposed as a single file.
// Each file may contain up to two lines, one for "some" pressure and one for "full" pressure.
// Each line contains several averages (over n seconds) and a total in µs.
//
// Example io pressure file:
// > some avg10=0.06 avg60=0.21 avg300=0.99 total=8537362
// > full avg10=0.00 avg60=0.13 avg300=0.96 total=8183134
import (
"fmt"
"io"
"io/ioutil"
"os"
"strings"
)
const lineFormat = "avg10=%f avg60=%f avg300=%f total=%d"
// PSILine is a single line of values as returned by /proc/pressure/*
// The Avg entries are averages over n seconds, as a percentage
// The Total line is in microseconds
type PSILine struct {
Avg10 float64
Avg60 float64
Avg300 float64
Total uint64
}
// PSIStats represent pressure stall information from /proc/pressure/*
// Some indicates the share of time in which at least some tasks are stalled
// Full indicates the share of time in which all non-idle tasks are stalled simultaneously
type PSIStats struct {
Some *PSILine
Full *PSILine
}
// PSIStatsForResource reads pressure stall information for the specified
// resource from /proc/pressure/<resource>. At time of writing this can be
// either "cpu", "memory" or "io".
func (fs FS) PSIStatsForResource(resource string) (PSIStats, error) {
file, err := os.Open(fs.proc.Path(fmt.Sprintf("%s/%s", "pressure", resource)))
if err != nil {
return PSIStats{}, fmt.Errorf("psi_stats: unavailable for %s", resource)
}
defer file.Close()
return parsePSIStats(resource, file)
}
// parsePSIStats parses the specified file for pressure stall information
func parsePSIStats(resource string, file io.Reader) (PSIStats, error) {
psiStats := PSIStats{}
stats, err := ioutil.ReadAll(file)
if err != nil {
return psiStats, fmt.Errorf("psi_stats: unable to read data for %s", resource)
}
for _, l := range strings.Split(string(stats), "\n") {
prefix := strings.Split(l, " ")[0]
switch prefix {
case "some":
psi := PSILine{}
_, err := fmt.Sscanf(l, fmt.Sprintf("some %s", lineFormat), &psi.Avg10, &psi.Avg60, &psi.Avg300, &psi.Total)
if err != nil {
return PSIStats{}, err
}
psiStats.Some = &psi
case "full":
psi := PSILine{}
_, err := fmt.Sscanf(l, fmt.Sprintf("full %s", lineFormat), &psi.Avg10, &psi.Avg60, &psi.Avg300, &psi.Total)
if err != nil {
return PSIStats{}, err
}
psiStats.Full = &psi
default:
// If we encounter a line with an unknown prefix, ignore it and move on
// Should new measurement types be added in the future we'll simply ignore them instead
// of erroring on retrieval
continue
}
}
return psiStats, nil
}

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 procfs package procfs
import ( import (
@ -5,6 +18,8 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/prometheus/procfs/internal/fs"
) )
// Originally, this USER_HZ value was dynamically retrieved via a sysconf call // Originally, this USER_HZ value was dynamically retrieved via a sysconf call
@ -82,15 +97,22 @@ type ProcStat struct {
// in clock ticks. // in clock ticks.
Starttime uint64 Starttime uint64
// Virtual memory size in bytes. // Virtual memory size in bytes.
VSize int VSize uint
// Resident set size in pages. // Resident set size in pages.
RSS int RSS int
fs FS proc fs.FS
} }
// NewStat returns the current status information of the process. // NewStat returns the current status information of the process.
//
// Deprecated: use NewStat() instead
func (p Proc) NewStat() (ProcStat, error) { func (p Proc) NewStat() (ProcStat, error) {
return p.Stat()
}
// Stat returns the current status information of the process.
func (p Proc) Stat() (ProcStat, error) {
f, err := os.Open(p.path("stat")) f, err := os.Open(p.path("stat"))
if err != nil { if err != nil {
return ProcStat{}, err return ProcStat{}, err
@ -105,7 +127,7 @@ func (p Proc) NewStat() (ProcStat, error) {
var ( var (
ignore int ignore int
s = ProcStat{PID: p.PID, fs: p.fs} s = ProcStat{PID: p.PID, proc: p.fs}
l = bytes.Index(data, []byte("(")) l = bytes.Index(data, []byte("("))
r = bytes.LastIndex(data, []byte(")")) r = bytes.LastIndex(data, []byte(")"))
) )
@ -151,7 +173,7 @@ func (p Proc) NewStat() (ProcStat, error) {
} }
// VirtualMemory returns the virtual memory size in bytes. // VirtualMemory returns the virtual memory size in bytes.
func (s ProcStat) VirtualMemory() int { func (s ProcStat) VirtualMemory() uint {
return s.VSize return s.VSize
} }
@ -162,7 +184,8 @@ func (s ProcStat) ResidentMemory() int {
// StartTime returns the unix timestamp of the process in seconds. // StartTime returns the unix timestamp of the process in seconds.
func (s ProcStat) StartTime() (float64, error) { func (s ProcStat) StartTime() (float64, error) {
stat, err := s.fs.NewStat() fs := FS{proc: s.proc}
stat, err := fs.Stat()
if err != nil { if err != nil {
return 0, err return 0, err
} }

162
vendor/github.com/prometheus/procfs/proc_status.go generated vendored Normal file
View file

@ -0,0 +1,162 @@
// 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 procfs
import (
"bytes"
"io/ioutil"
"os"
"strconv"
"strings"
)
// ProcStat provides status information about the process,
// read from /proc/[pid]/stat.
type ProcStatus struct {
// The process ID.
PID int
// The process name.
Name string
// Peak virtual memory size.
VmPeak uint64
// Virtual memory size.
VmSize uint64
// Locked memory size.
VmLck uint64
// Pinned memory size.
VmPin uint64
// Peak resident set size.
VmHWM uint64
// Resident set size (sum of RssAnnon RssFile and RssShmem).
VmRSS uint64
// Size of resident anonymous memory.
RssAnon uint64
// Size of resident file mappings.
RssFile uint64
// Size of resident shared memory.
RssShmem uint64
// Size of data segments.
VmData uint64
// Size of stack segments.
VmStk uint64
// Size of text segments.
VmExe uint64
// Shared library code size.
VmLib uint64
// Page table entries size.
VmPTE uint64
// Size of second-level page tables.
VmPMD uint64
// Swapped-out virtual memory size by anonymous private.
VmSwap uint64
// Size of hugetlb memory portions
HugetlbPages uint64
// Number of voluntary context switches.
VoluntaryCtxtSwitches uint64
// Number of involuntary context switches.
NonVoluntaryCtxtSwitches uint64
}
// NewStatus returns the current status information of the process.
func (p Proc) NewStatus() (ProcStatus, error) {
f, err := os.Open(p.path("status"))
if err != nil {
return ProcStatus{}, err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return ProcStatus{}, err
}
s := ProcStatus{PID: p.PID}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if !bytes.Contains([]byte(line), []byte(":")) {
continue
}
kv := strings.SplitN(line, ":", 2)
// removes spaces
k := string(strings.TrimSpace(kv[0]))
v := string(strings.TrimSpace(kv[1]))
// removes "kB"
v = string(bytes.Trim([]byte(v), " kB"))
// value to int when possible
// we can skip error check here, 'cause vKBytes is not used when value is a string
vKBytes, _ := strconv.ParseUint(v, 10, 64)
// convert kB to B
vBytes := vKBytes * 1024
s.fillStatus(k, v, vKBytes, vBytes)
}
return s, nil
}
func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintBytes uint64) {
switch k {
case "Name":
s.Name = vString
case "VmPeak":
s.VmPeak = vUintBytes
case "VmSize":
s.VmSize = vUintBytes
case "VmLck":
s.VmLck = vUintBytes
case "VmPin":
s.VmPin = vUintBytes
case "VmHWM":
s.VmHWM = vUintBytes
case "VmRSS":
s.VmRSS = vUintBytes
case "RssAnon":
s.RssAnon = vUintBytes
case "RssFile":
s.RssFile = vUintBytes
case "RssShmem":
s.RssShmem = vUintBytes
case "VmData":
s.VmData = vUintBytes
case "VmStk":
s.VmStk = vUintBytes
case "VmExe":
s.VmExe = vUintBytes
case "VmLib":
s.VmLib = vUintBytes
case "VmPTE":
s.VmPTE = vUintBytes
case "VmPMD":
s.VmPMD = vUintBytes
case "VmSwap":
s.VmSwap = vUintBytes
case "HugetlbPages":
s.HugetlbPages = vUintBytes
case "voluntary_ctxt_switches":
s.VoluntaryCtxtSwitches = vUint
case "nonvoluntary_ctxt_switches":
s.NonVoluntaryCtxtSwitches = vUint
}
}
// TotalCtxtSwitches returns the total context switch.
func (s ProcStatus) TotalCtxtSwitches() uint64 {
return s.VoluntaryCtxtSwitches + s.NonVoluntaryCtxtSwitches
}

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 procfs package procfs
import ( import (
@ -7,6 +20,8 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"github.com/prometheus/procfs/internal/fs"
) )
// CPUStat shows how much time the cpu spend in various stages. // CPUStat shows how much time the cpu spend in various stages.
@ -65,16 +80,6 @@ type Stat struct {
SoftIRQ SoftIRQStat SoftIRQ SoftIRQStat
} }
// NewStat returns kernel/system statistics read from /proc/stat.
func NewStat() (Stat, error) {
fs, err := NewFS(DefaultMountPoint)
if err != nil {
return Stat{}, err
}
return fs.NewStat()
}
// Parse a cpu statistics line and returns the CPUStat struct plus the cpu id (or -1 for the overall sum). // Parse a cpu statistics line and returns the CPUStat struct plus the cpu id (or -1 for the overall sum).
func parseCPUStat(line string) (CPUStat, int64, error) { func parseCPUStat(line string) (CPUStat, int64, error) {
cpuStat := CPUStat{} cpuStat := CPUStat{}
@ -136,11 +141,31 @@ func parseSoftIRQStat(line string) (SoftIRQStat, uint64, error) {
return softIRQStat, total, nil return softIRQStat, total, nil
} }
// NewStat returns an information about current kernel/system statistics. // NewStat returns information about current cpu/process statistics.
func (fs FS) NewStat() (Stat, error) { // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt //
// Deprecated: use fs.Stat() instead
func NewStat() (Stat, error) {
fs, err := NewFS(fs.DefaultProcMountPoint)
if err != nil {
return Stat{}, err
}
return fs.Stat()
}
f, err := os.Open(fs.Path("stat")) // NewStat returns information about current cpu/process statistics.
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
//
// Deprecated: use fs.Stat() instead
func (fs FS) NewStat() (Stat, error) {
return fs.Stat()
}
// Stat returns information about current cpu/process statistics.
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
func (fs FS) Stat() (Stat, error) {
f, err := os.Open(fs.proc.Path("stat"))
if err != nil { if err != nil {
return Stat{}, err return Stat{}, err
} }

View file

@ -1,11 +1,26 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Purpose: plain text tar format # Purpose: plain text tar format
# Limitations: - only suitable for text files, directories, and symlinks # Limitations: - only suitable for text files, directories, and symlinks
# - stores only filename, content, and mode # - stores only filename, content, and mode
# - not designed for untrusted input # - not designed for untrusted input
#
# Note: must work with bash version 3.2 (macOS) # Note: must work with bash version 3.2 (macOS)
# Copyright 2017 Roger Luethi
#
# 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.
set -o errexit -o nounset set -o errexit -o nounset
# Sanitize environment (for instance, standard sorting of glob matches) # Sanitize environment (for instance, standard sorting of glob matches)
@ -13,6 +28,55 @@ export LC_ALL=C
path="" path=""
CMD="" CMD=""
ARG_STRING="$*"
#------------------------------------------------------------------------------
# Not all sed implementations can work on null bytes. In order to make ttar
# work out of the box on macOS, use Python as a stream editor.
USE_PYTHON=0
PYTHON_CREATE_FILTER=$(cat << 'PCF'
#!/usr/bin/env python
import re
import sys
for line in sys.stdin:
line = re.sub(r'EOF', r'\EOF', line)
line = re.sub(r'NULLBYTE', r'\NULLBYTE', line)
line = re.sub('\x00', r'NULLBYTE', line)
sys.stdout.write(line)
PCF
)
PYTHON_EXTRACT_FILTER=$(cat << 'PEF'
#!/usr/bin/env python
import re
import sys
for line in sys.stdin:
line = re.sub(r'(?<!\\)NULLBYTE', '\x00', line)
line = re.sub(r'\\NULLBYTE', 'NULLBYTE', line)
line = re.sub(r'([^\\])EOF', r'\1', line)
line = re.sub(r'\\EOF', 'EOF', line)
sys.stdout.write(line)
PEF
)
function test_environment {
if [[ "$(echo "a" | sed 's/a/\x0/' | wc -c)" -ne 2 ]]; then
echo "WARNING sed unable to handle null bytes, using Python (slow)."
if ! which python >/dev/null; then
echo "ERROR Python not found. Aborting."
exit 2
fi
USE_PYTHON=1
fi
}
#------------------------------------------------------------------------------
function usage { function usage {
bname=$(basename "$0") bname=$(basename "$0")
@ -22,7 +86,10 @@ Usage: $bname [-C <DIR>] -c -f <ARCHIVE> <FILE...> (create archive)
$bname [-C <DIR>] -x -f <ARCHIVE> (extract archive) $bname [-C <DIR>] -x -f <ARCHIVE> (extract archive)
Options: Options:
-C <DIR> (change directory) -C <DIR> (change directory)
-v (verbose)
--recursive-unlink (recursively delete existing directory if path
collides with file or directory to extract)
Example: Change to sysfs directory, create ttar file from fixtures directory Example: Change to sysfs directory, create ttar file from fixtures directory
$bname -C sysfs -c -f sysfs/fixtures.ttar fixtures/ $bname -C sysfs -c -f sysfs/fixtures.ttar fixtures/
@ -45,7 +112,10 @@ function set_cmd {
CMD=$1 CMD=$1
} }
while getopts :cf:htxvC: opt; do unset VERBOSE
unset RECURSIVE_UNLINK
while getopts :cf:-:htxvC: opt; do
case $opt in case $opt in
c) c)
set_cmd "create" set_cmd "create"
@ -69,6 +139,18 @@ while getopts :cf:htxvC: opt; do
C) C)
CDIR=$OPTARG CDIR=$OPTARG
;; ;;
-)
case $OPTARG in
recursive-unlink)
RECURSIVE_UNLINK="yes"
;;
*)
echo -e "Error: invalid option -$OPTARG"
echo
usage 1
;;
esac
;;
*) *)
echo >&2 "ERROR: invalid option -$OPTARG" echo >&2 "ERROR: invalid option -$OPTARG"
echo echo
@ -142,14 +224,52 @@ function extract {
fi fi
while IFS= read -r line; do while IFS= read -r line; do
line_no=$(( line_no + 1 )) line_no=$(( line_no + 1 ))
local eof_without_newline
if [ "$size" -gt 0 ]; then if [ "$size" -gt 0 ]; then
echo "$line" >> "$path" if [[ "$line" =~ [^\\]EOF ]]; then
# An EOF not preceded by a backslash indicates that the line
# does not end with a newline
eof_without_newline=1
else
eof_without_newline=0
fi
# Replace NULLBYTE with null byte if at beginning of line
# Replace NULLBYTE with null byte unless preceded by backslash
# Remove one backslash in front of NULLBYTE (if any)
# Remove EOF unless preceded by backslash
# Remove one backslash in front of EOF
if [ $USE_PYTHON -eq 1 ]; then
echo -n "$line" | python -c "$PYTHON_EXTRACT_FILTER" >> "$path"
else
# The repeated pattern makes up for sed's lack of negative
# lookbehind assertions (for consecutive null bytes).
echo -n "$line" | \
sed -e 's/^NULLBYTE/\x0/g;
s/\([^\\]\)NULLBYTE/\1\x0/g;
s/\([^\\]\)NULLBYTE/\1\x0/g;
s/\\NULLBYTE/NULLBYTE/g;
s/\([^\\]\)EOF/\1/g;
s/\\EOF/EOF/g;
' >> "$path"
fi
if [[ "$eof_without_newline" -eq 0 ]]; then
echo >> "$path"
fi
size=$(( size - 1 )) size=$(( size - 1 ))
continue continue
fi fi
if [[ $line =~ ^Path:\ (.*)$ ]]; then if [[ $line =~ ^Path:\ (.*)$ ]]; then
path=${BASH_REMATCH[1]} path=${BASH_REMATCH[1]}
if [ -e "$path" ] || [ -L "$path" ]; then if [ -L "$path" ]; then
rm "$path"
elif [ -d "$path" ]; then
if [ "${RECURSIVE_UNLINK:-}" == "yes" ]; then
rm -r "$path"
else
# Safe because symlinks to directories are dealt with above
rmdir "$path"
fi
elif [ -e "$path" ]; then
rm "$path" rm "$path"
fi fi
elif [[ $line =~ ^Lines:\ (.*)$ ]]; then elif [[ $line =~ ^Lines:\ (.*)$ ]]; then
@ -187,11 +307,14 @@ function get_mode {
local mfile=$1 local mfile=$1
if [ -z "${STAT_OPTION:-}" ]; then if [ -z "${STAT_OPTION:-}" ]; then
if stat -c '%a' "$mfile" >/dev/null 2>&1; then if stat -c '%a' "$mfile" >/dev/null 2>&1; then
# GNU stat
STAT_OPTION='-c' STAT_OPTION='-c'
STAT_FORMAT='%a' STAT_FORMAT='%a'
else else
# BSD stat
STAT_OPTION='-f' STAT_OPTION='-f'
STAT_FORMAT='%A' # Octal output, user/group/other (omit file type, sticky bit)
STAT_FORMAT='%OLp'
fi fi
fi fi
stat "${STAT_OPTION}" "${STAT_FORMAT}" "$mfile" stat "${STAT_OPTION}" "${STAT_FORMAT}" "$mfile"
@ -200,6 +323,7 @@ function get_mode {
function _create { function _create {
shopt -s nullglob shopt -s nullglob
local mode local mode
local eof_without_newline
while (( "$#" )); do while (( "$#" )); do
file=$1 file=$1
if [ -L "$file" ]; then if [ -L "$file" ]; then
@ -223,8 +347,30 @@ function _create {
elif [ -f "$file" ]; then elif [ -f "$file" ]; then
echo "Path: $file" echo "Path: $file"
lines=$(wc -l "$file"|awk '{print $1}') lines=$(wc -l "$file"|awk '{print $1}')
eof_without_newline=0
if [[ "$(wc -c "$file"|awk '{print $1}')" -gt 0 ]] && \
[[ "$(tail -c 1 "$file" | wc -l)" -eq 0 ]]; then
eof_without_newline=1
lines=$((lines+1))
fi
echo "Lines: $lines" echo "Lines: $lines"
cat "$file" # Add backslash in front of EOF
# Add backslash in front of NULLBYTE
# Replace null byte with NULLBYTE
if [ $USE_PYTHON -eq 1 ]; then
< "$file" python -c "$PYTHON_CREATE_FILTER"
else
< "$file" \
sed 's/EOF/\\EOF/g;
s/NULLBYTE/\\NULLBYTE/g;
s/\x0/NULLBYTE/g;
'
fi
if [[ "$eof_without_newline" -eq 1 ]]; then
# Finish line with EOF to indicate that the original line did
# not end with a linefeed
echo "EOF"
fi
mode=$(get_mode "$file") mode=$(get_mode "$file")
echo "Mode: $mode" echo "Mode: $mode"
vecho "$mode $file" vecho "$mode $file"
@ -249,9 +395,12 @@ function create {
rm "$ttar_file" rm "$ttar_file"
fi fi
exec > "$ttar_file" exec > "$ttar_file"
echo "# Archive created by ttar $ARG_STRING"
_create "$@" _create "$@"
} }
test_environment
if [ -n "${CDIR:-}" ]; then if [ -n "${CDIR:-}" ]; then
if [[ "$ARCHIVE" != /* ]]; then if [[ "$ARCHIVE" != /* ]]; then
# Relative path: preserve the archive's location before changing # Relative path: preserve the archive's location before changing

View file

@ -97,7 +97,7 @@ func NewXfrmStat() (XfrmStat, error) {
// NewXfrmStat reads the xfrm_stat statistics from the 'proc' filesystem. // NewXfrmStat reads the xfrm_stat statistics from the 'proc' filesystem.
func (fs FS) NewXfrmStat() (XfrmStat, error) { func (fs FS) NewXfrmStat() (XfrmStat, error) {
file, err := os.Open(fs.Path("net/xfrm_stat")) file, err := os.Open(fs.proc.Path("net/xfrm_stat"))
if err != nil { if err != nil {
return XfrmStat{}, err return XfrmStat{}, err
} }
@ -113,7 +113,7 @@ func (fs FS) NewXfrmStat() (XfrmStat, error) {
if len(fields) != 2 { if len(fields) != 2 {
return XfrmStat{}, fmt.Errorf( return XfrmStat{}, fmt.Errorf(
"couldnt parse %s line %s", file.Name(), s.Text()) "couldn't parse %s line %s", file.Name(), s.Text())
} }
name := fields[0] name := fields[0]

8
vendor/vendor.json vendored
View file

@ -330,13 +330,15 @@
{"path":"github.com/posener/complete/cmd","checksumSHA1":"NB7uVS0/BJDmNu68vPAlbrq4TME=","revision":"9f41f7636a724791a3b8b1d35e84caa1124f0d3c","revisionTime":"2017-08-29T17:11:12Z"}, {"path":"github.com/posener/complete/cmd","checksumSHA1":"NB7uVS0/BJDmNu68vPAlbrq4TME=","revision":"9f41f7636a724791a3b8b1d35e84caa1124f0d3c","revisionTime":"2017-08-29T17:11:12Z"},
{"path":"github.com/posener/complete/cmd/install","checksumSHA1":"gSX86Xl0w9hvtntdT8h23DZtSag=","revision":"9f41f7636a724791a3b8b1d35e84caa1124f0d3c","revisionTime":"2017-08-29T17:11:12Z"}, {"path":"github.com/posener/complete/cmd/install","checksumSHA1":"gSX86Xl0w9hvtntdT8h23DZtSag=","revision":"9f41f7636a724791a3b8b1d35e84caa1124f0d3c","revisionTime":"2017-08-29T17:11:12Z"},
{"path":"github.com/posener/complete/match","checksumSHA1":"DMo94FwJAm9ZCYCiYdJU2+bh4no=","revision":"9f41f7636a724791a3b8b1d35e84caa1124f0d3c","revisionTime":"2017-08-29T17:11:12Z"}, {"path":"github.com/posener/complete/match","checksumSHA1":"DMo94FwJAm9ZCYCiYdJU2+bh4no=","revision":"9f41f7636a724791a3b8b1d35e84caa1124f0d3c","revisionTime":"2017-08-29T17:11:12Z"},
{"path":"github.com/prometheus/client_golang/prometheus","checksumSHA1":"+5YXakGoZBEMrHp6h64TC5GSFfg=","revision":"671c87b04728565380d95c621edc687bfa00664c","revisionTime":"2017-09-07T15:06:25Z"}, {"path":"github.com/prometheus/client_golang/prometheus","checksumSHA1":"5dHjKxShYVWVB1Fb00dAnR6kqVk=","revision":"2641b987480bca71fb39738eb8c8b0d577cb1d76","revisionTime":"2019-06-07T14:56:44Z","version":"v0.9.4","versionExact":"v0.9.4"},
{"path":"github.com/prometheus/client_golang/prometheus/promhttp","checksumSHA1":"wsAkYlRRUNx+OAuUOIqdjO7dICM=","revision":"671c87b04728565380d95c621edc687bfa00664c","revisionTime":"2017-09-07T15:06:25Z"}, {"path":"github.com/prometheus/client_golang/prometheus/internal","checksumSHA1":"UBqhkyjCz47+S19MVTigxJ2VjVQ=","revision":"2641b987480bca71fb39738eb8c8b0d577cb1d76","revisionTime":"2019-06-07T14:56:44Z","version":"v0.9.4","versionExact":"v0.9.4"},
{"path":"github.com/prometheus/client_golang/prometheus/promhttp","checksumSHA1":"V51yx4gq61QCD9clxnps792Eq2Y=","revision":"2641b987480bca71fb39738eb8c8b0d577cb1d76","revisionTime":"2019-06-07T14:56:44Z","version":"v0.9.4","versionExact":"v0.9.4"},
{"path":"github.com/prometheus/client_model/go","checksumSHA1":"DvwvOlPNAgRntBzt3b3OSRMS2N4=","revision":"6f3806018612930941127f2a7c6c453ba2c527d2","revisionTime":"2017-02-16T18:52:47Z"}, {"path":"github.com/prometheus/client_model/go","checksumSHA1":"DvwvOlPNAgRntBzt3b3OSRMS2N4=","revision":"6f3806018612930941127f2a7c6c453ba2c527d2","revisionTime":"2017-02-16T18:52:47Z"},
{"path":"github.com/prometheus/common/expfmt","checksumSHA1":"xfnn0THnqNwjwimeTClsxahYrIo=","revision":"2f17f4a9d485bf34b4bfaccc273805040e4f86c8","revisionTime":"2017-09-08T16:18:22Z"}, {"path":"github.com/prometheus/common/expfmt","checksumSHA1":"xfnn0THnqNwjwimeTClsxahYrIo=","revision":"2f17f4a9d485bf34b4bfaccc273805040e4f86c8","revisionTime":"2017-09-08T16:18:22Z"},
{"path":"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg","checksumSHA1":"GWlM3d2vPYyNATtTFgftS10/A9w=","revision":"2f17f4a9d485bf34b4bfaccc273805040e4f86c8","revisionTime":"2017-09-08T16:18:22Z"}, {"path":"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg","checksumSHA1":"GWlM3d2vPYyNATtTFgftS10/A9w=","revision":"2f17f4a9d485bf34b4bfaccc273805040e4f86c8","revisionTime":"2017-09-08T16:18:22Z"},
{"path":"github.com/prometheus/common/model","checksumSHA1":"3VoqH7TFfzA6Ds0zFzIbKCUvBmw=","revision":"2f17f4a9d485bf34b4bfaccc273805040e4f86c8","revisionTime":"2017-09-08T16:18:22Z"}, {"path":"github.com/prometheus/common/model","checksumSHA1":"3VoqH7TFfzA6Ds0zFzIbKCUvBmw=","revision":"2f17f4a9d485bf34b4bfaccc273805040e4f86c8","revisionTime":"2017-09-08T16:18:22Z"},
{"path":"github.com/prometheus/procfs","checksumSHA1":"ihxJIjxtbEYdQKwA0D0nRipj95I=","revision":"e645f4e5aaa8506fc71d6edbc5c4ff02c04c46f2","revisionTime":"2017-07-03T10:12:42Z"}, {"path":"github.com/prometheus/procfs","checksumSHA1":"WB7dFqkmD3R514xql9YM3ZP1dDM=","revision":"833678b5bb319f2d20a475cb165c6cc59c2cc77c","revisionTime":"2019-05-31T16:30:47Z","version":"v0.0.2","versionExact":"v0.0.2"},
{"path":"github.com/prometheus/procfs/internal/fs","checksumSHA1":"Kmjs49lbjGmlgUPx3pks0tVDed0=","revision":"833678b5bb319f2d20a475cb165c6cc59c2cc77c","revisionTime":"2019-05-31T16:30:47Z","version":"v0.0.2","versionExact":"v0.0.2"},
{"path":"github.com/prometheus/procfs/xfs","checksumSHA1":"xCiFAAwVTrjsfZT1BIJQ3DgeNCY=","revision":"e645f4e5aaa8506fc71d6edbc5c4ff02c04c46f2","revisionTime":"2017-07-03T10:12:42Z"}, {"path":"github.com/prometheus/procfs/xfs","checksumSHA1":"xCiFAAwVTrjsfZT1BIJQ3DgeNCY=","revision":"e645f4e5aaa8506fc71d6edbc5c4ff02c04c46f2","revisionTime":"2017-07-03T10:12:42Z"},
{"path":"github.com/rs/cors","checksumSHA1":"I778b2sbNN/yjwKSdb3y7hz2yUQ=","revision":"eabcc6af4bbe5ad3a949d36450326a2b0b9894b8","revisionTime":"2017-08-01T07:32:01Z"}, {"path":"github.com/rs/cors","checksumSHA1":"I778b2sbNN/yjwKSdb3y7hz2yUQ=","revision":"eabcc6af4bbe5ad3a949d36450326a2b0b9894b8","revisionTime":"2017-08-01T07:32:01Z"},
{"path":"github.com/ryanuber/columnize","checksumSHA1":"M57Rrfc8Z966p+IBtQ91QOcUtcg=","comment":"v2.0.1-8-g983d3a5","revision":"abc90934186a77966e2beeac62ed966aac0561d5","revisionTime":"2017-07-03T20:58:27Z"}, {"path":"github.com/ryanuber/columnize","checksumSHA1":"M57Rrfc8Z966p+IBtQ91QOcUtcg=","comment":"v2.0.1-8-g983d3a5","revision":"abc90934186a77966e2beeac62ed966aac0561d5","revisionTime":"2017-07-03T20:58:27Z"},