Skip to content

Commit

Permalink
Added IteratableGatherer for future zero alloc implementations.
Browse files Browse the repository at this point in the history
Signed-off-by: Bartlomiej Plotka <[email protected]>
  • Loading branch information
bwplotka committed Oct 22, 2021
1 parent 679eb0d commit 278c4dd
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 9 deletions.
39 changes: 30 additions & 9 deletions prometheus/promhttp/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -191,14 +210,16 @@ 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())) {
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
}
}
Expand Down
72 changes: 72 additions & 0 deletions prometheus/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 278c4dd

Please sign in to comment.