Skip to content

Commit eec0f90

Browse files
committed
Added IteratableGatherer for future zero alloc implementations.
Signed-off-by: Bartlomiej Plotka <bwplotka@gmail.com>
1 parent 679eb0d commit eec0f90

File tree

2 files changed

+102
-9
lines changed

2 files changed

+102
-9
lines changed

prometheus/promhttp/http.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ func Handler() http.Handler {
8484
// instrumentation. Use the InstrumentMetricHandler function to apply the same
8585
// kind of instrumentation as it is used by the Handler function.
8686
func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
87+
return HandlerForIteratable(prometheus.ToIteratableGatherer(reg), opts)
88+
}
89+
90+
// HandlerForIteratable returns an uninstrumented http.Handler for the provided
91+
// IteratableGatherer. The behavior of the Handler is defined by the provided
92+
// HandlerOpts. Thus, HandlerFor is useful to create http.Handlers for custom
93+
// Gatherers, with non-default HandlerOpts, and/or with custom (or no)
94+
// instrumentation. Use the InstrumentMetricHandler function to apply the same
95+
// kind of instrumentation as it is used by the Handler function.
96+
func HandlerForIteratable(reg prometheus.IteratableGatherer, opts HandlerOpts) http.Handler {
8797
var (
8898
inFlightSem chan struct{}
8999
errCnt = prometheus.NewCounterVec(
@@ -123,7 +133,8 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
123133
return
124134
}
125135
}
126-
mfs, err := reg.Gather()
136+
137+
iter, err := reg.GatherIterate()
127138
if err != nil {
128139
if opts.ErrorLog != nil {
129140
opts.ErrorLog.Println("error gathering metrics:", err)
@@ -133,7 +144,7 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
133144
case PanicOnError:
134145
panic(err)
135146
case ContinueOnError:
136-
if len(mfs) == 0 {
147+
if iter == nil {
137148
// Still report the error if no metrics have been gathered.
138149
httpError(rsp, err)
139150
return
@@ -169,14 +180,22 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
169180

170181
// handleError handles the error according to opts.ErrorHandling
171182
// and returns true if we have to abort after the handling.
172-
handleError := func(err error) bool {
183+
handleError := func(encoding bool, err error) bool {
173184
if err == nil {
174185
return false
175186
}
176-
if opts.ErrorLog != nil {
177-
opts.ErrorLog.Println("error encoding and sending metric family:", err)
187+
if encoding {
188+
if opts.ErrorLog != nil {
189+
opts.ErrorLog.Println("error encoding and sending metric family:", err)
190+
}
191+
errCnt.WithLabelValues("encoding").Inc()
192+
} else {
193+
if opts.ErrorLog != nil {
194+
opts.ErrorLog.Println("error gathering metrics:", err)
195+
}
196+
errCnt.WithLabelValues("gathering").Inc()
178197
}
179-
errCnt.WithLabelValues("encoding").Inc()
198+
180199
switch opts.ErrorHandling {
181200
case PanicOnError:
182201
panic(err)
@@ -191,14 +210,16 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
191210
return false
192211
}
193212

194-
for _, mf := range mfs {
195-
if handleError(enc.Encode(mf)) {
213+
for iter.Next() {
214+
if handleError(true, enc.Encode(iter.At())) {
196215
return
197216
}
198217
}
218+
handleError(false, iter.Err())
219+
199220
if closer, ok := enc.(expfmt.Closer); ok {
200221
// This in particular takes care of the final "# EOF\n" line for OpenMetrics.
201-
if handleError(closer.Close()) {
222+
if handleError(true, closer.Close()) {
202223
return
203224
}
204225
}

prometheus/registry.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,78 @@ type Gatherer interface {
161161
Gather() ([]*dto.MetricFamily, error)
162162
}
163163

164+
// IteratableGatherer is the interface for the part of a registry in charge of gathering
165+
// the collected metrics into a number of MetricFamilies. The IteratableGatherer interface
166+
// comes with the same general implication as described for the Registerer
167+
// interface.
168+
type IteratableGatherer interface {
169+
// GatherIterate allows iterating over a lexicographically sorted, uniquely named
170+
// dto.MetricFamily that are obtained through calls the Collect method
171+
// of the registered Collectors. GatherIterate ensures that the
172+
// iterator is valid until exhausting (until Next is false).
173+
// As an exception to the strict consistency requirements described for
174+
// metric.Desc, GatherIterate will tolerate different sets of label names for
175+
// metrics of the same metric family.
176+
//
177+
// Even if an error occurs, GatherIterate allows to iterate over as many metrics as
178+
// possible. Hence, if a non-nil error is returned, the returned
179+
// MetricFamilyIterator could be nil (in case of a fatal error that
180+
// prevented any meaningful metric collection) or iteratable over a number of
181+
// MetricFamily protobufs, some of which might be incomplete, and some
182+
// might be missing altogether. The returned error (which might be a
183+
// MultiError) explains the details. Note that this is mostly useful for
184+
// debugging purposes. If the gathered protobufs are to be used for
185+
// exposition in actual monitoring, it is almost always better to not
186+
// expose an incomplete result and instead disregard the returned
187+
// MetricFamily protobufs in case the returned error is non-nil.
188+
//
189+
// GatherIterate allows implementations to use zero-alloc caching and
190+
// other efficiency techniques.
191+
//
192+
// Iterated dto.MetricFamily is read only and valid only until you invoke another
193+
// Next(). Note that consumer is *always* required to exhaust iterator to release all resources.
194+
GatherIterate() (MetricFamilyIterator, error)
195+
}
196+
197+
// MetricFamilyIterator ...
198+
type MetricFamilyIterator interface {
199+
Next() bool
200+
At() *dto.MetricFamily
201+
Err() error
202+
}
203+
204+
func ToIteratableGatherer(g Gatherer) IteratableGatherer {
205+
return &iteratableGathererAdapter{g:g}
206+
}
207+
208+
type iteratableGathererAdapter struct {
209+
g Gatherer
210+
}
211+
212+
func(a *iteratableGathererAdapter) GatherIterate() (MetricFamilyIterator, error) {
213+
mfs, err := a.g.Gather()
214+
return &mfIterator{mfs: mfs, i: -1}, err
215+
}
216+
217+
type mfIterator struct {
218+
mfs []*dto.MetricFamily
219+
i int
220+
}
221+
222+
func (m *mfIterator) Next() bool {
223+
if m.i+1 >= len(m.mfs) {
224+
return false
225+
}
226+
m.i++
227+
return true
228+
}
229+
230+
func (m *mfIterator) At() *dto.MetricFamily {
231+
return m.mfs[m.i]
232+
}
233+
234+
func (m *mfIterator) Err() error { return nil }
235+
164236
// Register registers the provided Collector with the DefaultRegisterer.
165237
//
166238
// Register is a shortcut for DefaultRegisterer.Register(c). See there for more

0 commit comments

Comments
 (0)