-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Closed
Labels
Milestone
Description
This is the current Collector
interface:
type Collector interface {
Describe(chan<- *Desc)
Collect(chan<- Metric)
}
The main reason for having channels there was that back in 2013, channels were cool, so I was primarily looking for neat ways to make use of them rather than avoiding them in APIs. While the current interface has some merits, the pattern used here is very unconventional, and now that breaking changes will happen anyway, we have the chance of changing the interface. In the following, I'll list pros and cons to come to an informed decision.
The new interface would look like:
type Collector interface {
Describe() []*Desc
Collect() []Metric
}
Reasons for changing the interface
- Channels in APIs are generally frowned upon in the community. Users get irritated just by looking at it. The usefulness of a chan API needs to outweigh the amount of irritation.
- Channels suggest that concurrency is part of the game, which is only partially true here.
- Performance/allocation penalty of the slice interface is not overly relevant as registration and collection happens relatively rare. (Exception would be high-frequency scrape scenarios.)
Reasons for keeping the interface as is
- This would be one of the more invasive changes. Many code lines have to be changed by those who implemented custom collectors, see code examples below. The benefits of changing the interface need to be convincing enough to justify this work.
- The code for chaining collectors is quite neat, see below.
- Fewer slice juggling required, has an advantage in terms of allocations and code complexity.
- Concurrent Metric collection (as it is used by the registry) is more straight-forward with the channel being passed around. Also, metrics are already encoded into protobufs concurrently with the collection still running (which probably only matters with really large scrapes).
- Only "power users" get in touch with the Collector interface, which limits the damage of the unusual API.
Appendix: Code examples
Sequential collection with old interface
func (c *C) Collect(m chan<- prometheus.Metric) {
m <- c.constMetric
c.subCollector.Collect(m)
c.anotherSubCollector.Collect(m)
}
Sequential collection with new interface
func (c *C) Collect() []prometheus.Metric {
var m []prometheus.Metric
m = append(m, c.constMetric)
m = append(m, c.subCollector.Collect()...)
m = append(m, c.anotherSubCollector.Collect(m)...)
return m
}
or more compact (but arguably also more confusing):
func (c *C) Collect() []prometheus.Metric {
m := []promtheus.Metric{c.constMetric}
m = append(m, c.subCollector.Collect()...)
return append(m, c.anotherSubCollector.Collect(m)...)
}
Concurrent collection with old interface
func (c *C) Collect(m chan<- prometheus.Metric) {
var wg sync.WaitGroup
wg.Add(len(c.colls))
for _, coll := range c.colls {
go func(coll prometheus.Collector) {
coll.Collect(m) // Protobuf encoding already starting here.
wg.Done()
}(coll)
}
wg.Wait()
}
Concurrent collection with new interface
func (c *ApacheCollector) Collect() []prometheus.Metric {
ch := make(chan []prometheus.Metric)
for _, coll := range c.colls {
go func(coll prometheus.Collector) {
ch <- coll.Collect()
}(coll)
}
var m []prometheus.Metric
for _ = range c.colls {
m = append(m, <-ch...)
}
return m // Only now can the registry start encoding.
}
anacrolix, leelynne, AlekSi, lukeyeager and tetafro