aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/prometheus/client_golang/prometheus/registry.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/prometheus/client_golang/prometheus/registry.go')
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/registry.go351
1 files changed, 193 insertions, 158 deletions
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/registry.go b/vendor/github.com/prometheus/client_golang/prometheus/registry.go
index c84a442..fdb7bad 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/registry.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/registry.go
@@ -18,6 +18,7 @@ import (
"errors"
"fmt"
"os"
+ "runtime"
"sort"
"sync"
"unicode/utf8"
@@ -36,13 +37,14 @@ const (
// DefaultRegisterer and DefaultGatherer are the implementations of the
// Registerer and Gatherer interface a number of convenience functions in this
// package act on. Initially, both variables point to the same Registry, which
-// has a process collector (see NewProcessCollector) and a Go collector (see
-// NewGoCollector) already registered. This approach to keep default instances
-// as global state mirrors the approach of other packages in the Go standard
-// library. Note that there are caveats. Change the variables with caution and
-// only if you understand the consequences. Users who want to avoid global state
-// altogether should not use the convenience function and act on custom
-// instances instead.
+// has a process collector (currently on Linux only, see NewProcessCollector)
+// and a Go collector (see NewGoCollector, in particular the note about
+// stop-the-world implication with Go versions older than 1.9) already
+// registered. This approach to keep default instances as global state mirrors
+// the approach of other packages in the Go standard library. Note that there
+// are caveats. Change the variables with caution and only if you understand the
+// consequences. Users who want to avoid global state altogether should not use
+// the convenience functions and act on custom instances instead.
var (
defaultRegistry = NewRegistry()
DefaultRegisterer Registerer = defaultRegistry
@@ -124,15 +126,23 @@ type Registerer interface {
type Gatherer interface {
// Gather calls the Collect method of the registered Collectors and then
// gathers the collected metrics into a lexicographically sorted slice
- // of MetricFamily protobufs. Even if an error occurs, Gather attempts
- // to gather as many metrics as possible. Hence, if a non-nil error is
- // returned, the returned 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. In scenarios
- // where complete collection is critical, the returned MetricFamily
- // protobufs should be disregarded if the returned error is non-nil.
+ // of uniquely named MetricFamily protobufs. Gather ensures that the
+ // returned slice is valid and self-consistent so that it can be used
+ // for valid exposition. As an exception to the strict consistency
+ // requirements described for metric.Desc, Gather will tolerate
+ // different sets of label names for metrics of the same metric family.
+ //
+ // Even if an error occurs, Gather attempts to gather as many metrics as
+ // possible. Hence, if a non-nil error is returned, the returned
+ // 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)
}
@@ -202,6 +212,13 @@ func (errs MultiError) Error() 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
// 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
@@ -361,29 +378,18 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
var (
metricChan = make(chan Metric, capMetricChan)
metricHashes = map[uint64]struct{}{}
- dimHashes = map[string]uint64{}
wg sync.WaitGroup
errs MultiError // The collected errors to return in the end.
registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
)
r.mtx.RLock()
+ goroutineBudget := len(r.collectorsByID)
metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
-
- // Scatter.
- // (Collectors could be complex and slow, so we call them all at once.)
- wg.Add(len(r.collectorsByID))
- go func() {
- wg.Wait()
- close(metricChan)
- }()
+ collectors := make(chan Collector, len(r.collectorsByID))
for _, collector := range r.collectorsByID {
- go func(collector Collector) {
- defer wg.Done()
- collector.Collect(metricChan)
- }(collector)
+ collectors <- collector
}
-
// In case pedantic checks are enabled, we have to copy the map before
// giving up the RLock.
if r.pedanticChecksEnabled {
@@ -392,127 +398,173 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
registeredDescIDs[id] = struct{}{}
}
}
-
r.mtx.RUnlock()
+ wg.Add(goroutineBudget)
+
+ collectWorker := func() {
+ for {
+ select {
+ case collector := <-collectors:
+ collector.Collect(metricChan)
+ wg.Done()
+ default:
+ return
+ }
+ }
+ }
+
+ // Start the first worker now to make sure at least one is running.
+ go collectWorker()
+ goroutineBudget--
+
+ // Close the metricChan once all collectors are collected.
+ go func() {
+ wg.Wait()
+ close(metricChan)
+ }()
+
// Drain metricChan in case of premature return.
defer func() {
for range metricChan {
}
}()
- // Gather.
- for metric := range metricChan {
- // This could be done concurrently, too, but it required locking
- // of metricFamiliesByName (and of metricHashes if checks are
- // enabled). Most likely not worth it.
- desc := metric.Desc()
- dtoMetric := &dto.Metric{}
- if err := metric.Write(dtoMetric); err != nil {
- errs = append(errs, fmt.Errorf(
- "error collecting metric %v: %s", desc, err,
- ))
- continue
- }
- metricFamily, ok := metricFamiliesByName[desc.fqName]
- if ok {
- if metricFamily.GetHelp() != desc.help {
- errs = append(errs, fmt.Errorf(
- "collected metric %s %s has help %q but should have %q",
- desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
- ))
- continue
+collectLoop:
+ for {
+ select {
+ case metric, ok := <-metricChan:
+ if !ok {
+ // metricChan is closed, we are done.
+ break collectLoop
}
- // 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,
+ errs.Append(processMetric(
+ metric, metricFamiliesByName,
+ metricHashes,
+ registeredDescIDs,
+ ))
+ default:
+ if goroutineBudget <= 0 || len(collectors) == 0 {
+ // All collectors are already being worked on or
+ // we have already as many goroutines started as
+ // there are collectors. Just process metrics
+ // from now on.
+ for metric := range metricChan {
+ errs.Append(processMetric(
+ metric, metricFamiliesByName,
+ metricHashes,
+ registeredDescIDs,
))
- 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
+ break collectLoop
}
- metricFamiliesByName[desc.fqName] = metricFamily
+ // Start more workers.
+ go collectWorker()
+ goroutineBudget--
+ runtime.Gosched()
}
- if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes, dimHashes); err != nil {
- errs = append(errs, err)
- continue
+ }
+ return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
+}
+
+// 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()
+ 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 {
+ 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(),
+ )
}
- 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
+ // 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,
+ )
}
- if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
- errs = append(errs, err)
- continue
+ 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")
}
- metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
+ } 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:
+ return fmt.Errorf("empty metric collected: %s", dtoMetric)
+ }
+ metricFamiliesByName[desc.fqName] = metricFamily
}
- return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
+ 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
@@ -538,7 +590,6 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
var (
metricFamiliesByName = map[string]*dto.MetricFamily{}
metricHashes = map[uint64]struct{}{}
- dimHashes = map[string]uint64{}
errs MultiError // The collected errors to return in the end.
)
@@ -578,7 +629,7 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
metricFamiliesByName[mf.GetName()] = existingMF
}
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)
continue
}
@@ -655,18 +706,13 @@ func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily)
}
// 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 already in the provided metricHashes, an error
-// is returned. If not, it is added to metricHashes. The provided dimHashes maps
-// 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.
+// is returned. If not, it is added to metricHashes.
func checkMetricConsistency(
metricFamily *dto.MetricFamily,
dtoMetric *dto.Metric,
metricHashes map[uint64]struct{},
- dimHashes map[string]uint64,
) error {
// Type consistency with metric family.
if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
@@ -681,24 +727,23 @@ func checkMetricConsistency(
}
for _, labelPair := range dtoMetric.GetLabel() {
- if !utf8.ValidString(*labelPair.Value) {
- return fmt.Errorf("collected metric's label %s is not utf8: %#v", *labelPair.Name, *labelPair.Value)
+ if !utf8.ValidString(labelPair.GetValue()) {
+ return fmt.Errorf("collected metric's label %s is not utf8: %#v", labelPair.GetName(), labelPair.GetValue())
}
}
- // 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 = hashAdd(h, metricFamily.GetName())
h = hashAddByte(h, separatorByte)
- dh := hashNew()
// Make sure label pairs are sorted. We depend on it for the consistency
// check.
sort.Sort(LabelPairSorter(dtoMetric.Label))
for _, lp := range dtoMetric.Label {
+ h = hashAdd(h, lp.GetName())
+ h = hashAddByte(h, separatorByte)
h = hashAdd(h, lp.GetValue())
h = hashAddByte(h, separatorByte)
- dh = hashAdd(dh, lp.GetName())
- dh = hashAddByte(dh, separatorByte)
}
if _, exists := metricHashes[h]; exists {
return fmt.Errorf(
@@ -706,16 +751,6 @@ func checkMetricConsistency(
metricFamily.GetName(), 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{}{}
return nil
}