From 187ecc4ac97e4e9ced26411f2a9587db8486d538 Mon Sep 17 00:00:00 2001 From: Bartlomiej Plotka Date: Fri, 22 Oct 2021 16:39:59 +0200 Subject: [PATCH] Added IteratableGatherer for future zero alloc implementations. Signed-off-by: Bartlomiej Plotka --- prometheus/promhttp/http.go | 40 ++++++++++++++++----- prometheus/registry.go | 72 +++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/prometheus/promhttp/http.go b/prometheus/promhttp/http.go index d86d0cf4b..b7ee51122 100644 --- a/prometheus/promhttp/http.go +++ b/prometheus/promhttp/http.go @@ -84,6 +84,16 @@ func Handler() http.Handler { // 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 { + return HandlerForIteratable(prometheus.ToIteratableGatherer(reg), opts) +} + +// HandlerForIteratable returns an uninstrumented http.Handler for the provided +// IteratableGatherer. 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 HandlerForIteratable(reg prometheus.IteratableGatherer, opts HandlerOpts) http.Handler { var ( inFlightSem chan struct{} errCnt = prometheus.NewCounterVec( @@ -123,7 +133,8 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { return } } - mfs, err := reg.Gather() + + iter, err := reg.GatherIterate() if err != nil { if opts.ErrorLog != nil { opts.ErrorLog.Println("error gathering metrics:", err) @@ -133,7 +144,7 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { case PanicOnError: panic(err) case ContinueOnError: - if len(mfs) == 0 { + if iter == nil { // Still report the error if no metrics have been gathered. httpError(rsp, err) return @@ -169,14 +180,22 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { // handleError handles the error according to opts.ErrorHandling // and returns true if we have to abort after the handling. - handleError := func(err error) bool { + handleError := func(encoding bool, err error) bool { if err == nil { return false } - if opts.ErrorLog != nil { - opts.ErrorLog.Println("error encoding and sending metric family:", err) + if encoding { + if opts.ErrorLog != nil { + opts.ErrorLog.Println("error encoding and sending metric family:", err) + } + errCnt.WithLabelValues("encoding").Inc() + } else { + if opts.ErrorLog != nil { + opts.ErrorLog.Println("error gathering metrics:", err) + } + errCnt.WithLabelValues("gathering").Inc() } - errCnt.WithLabelValues("encoding").Inc() + switch opts.ErrorHandling { case PanicOnError: panic(err) @@ -191,14 +210,17 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { return false } - for _, mf := range mfs { - if handleError(enc.Encode(mf)) { + for iter.Next() { + if handleError(true, enc.Encode(iter.At())) { + for iter.Next() {} // Exhaust iterator. return } } + handleError(false, iter.Err()) + if closer, ok := enc.(expfmt.Closer); ok { // This in particular takes care of the final "# EOF\n" line for OpenMetrics. - if handleError(closer.Close()) { + if handleError(true, closer.Close()) { return } } diff --git a/prometheus/registry.go b/prometheus/registry.go index 383a7f594..689c8eb23 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -161,6 +161,78 @@ type Gatherer interface { Gather() ([]*dto.MetricFamily, error) } +// IteratableGatherer is the interface for the part of a registry in charge of gathering +// the collected metrics into a number of MetricFamilies. The IteratableGatherer interface +// comes with the same general implication as described for the Registerer +// interface. +type IteratableGatherer interface { + // GatherIterate allows iterating over a lexicographically sorted, uniquely named + // dto.MetricFamily that are obtained through calls the Collect method + // of the registered Collectors. GatherIterate ensures that the + // iterator is valid until exhausting (until Next is false). + // As an exception to the strict consistency requirements described for + // metric.Desc, GatherIterate will tolerate different sets of label names for + // metrics of the same metric family. + // + // Even if an error occurs, GatherIterate allows to iterate over as many metrics as + // possible. Hence, if a non-nil error is returned, the returned + // MetricFamilyIterator could be nil (in case of a fatal error that + // prevented any meaningful metric collection) or iteratable over 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. + // + // GatherIterate allows implementations to use zero-alloc caching and + // other efficiency techniques. + // + // Iterated dto.MetricFamily is read only and valid only until you invoke another + // Next(). Note that consumer is *always* required to exhaust iterator to release all resources. + GatherIterate() (MetricFamilyIterator, error) +} + +// MetricFamilyIterator ... +type MetricFamilyIterator interface { + Next() bool + At() *dto.MetricFamily + Err() error +} + +func ToIteratableGatherer(g Gatherer) IteratableGatherer { + return &iteratableGathererAdapter{g:g} +} + +type iteratableGathererAdapter struct { + g Gatherer +} + +func(a *iteratableGathererAdapter) GatherIterate() (MetricFamilyIterator, error) { + mfs, err := a.g.Gather() + return &mfIterator{mfs: mfs, i: -1}, err +} + +type mfIterator struct { + mfs []*dto.MetricFamily + i int +} + +func (m *mfIterator) Next() bool { + if m.i+1 >= len(m.mfs) { + return false + } + m.i++ + return true +} + +func (m *mfIterator) At() *dto.MetricFamily { + return m.mfs[m.i] +} + +func (m *mfIterator) Err() error { return nil } + // Register registers the provided Collector with the DefaultRegisterer. // // Register is a shortcut for DefaultRegisterer.Register(c). See there for more