blob: b5e70b93fa13869ea93dbdf44094fe63095edb25 [file] [log] [blame]
khenaidooffe076b2019-01-15 16:08:08 -05001// Copyright 2014 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package prometheus
15
16import (
17 "bytes"
18 "fmt"
19 "io/ioutil"
20 "os"
21 "path/filepath"
22 "runtime"
23 "sort"
24 "strings"
25 "sync"
26 "unicode/utf8"
27
28 "github.com/golang/protobuf/proto"
29 "github.com/prometheus/common/expfmt"
30
31 dto "github.com/prometheus/client_model/go"
32
33 "github.com/prometheus/client_golang/prometheus/internal"
34)
35
36const (
37 // Capacity for the channel to collect metrics and descriptors.
38 capMetricChan = 1000
39 capDescChan = 10
40)
41
42// DefaultRegisterer and DefaultGatherer are the implementations of the
43// Registerer and Gatherer interface a number of convenience functions in this
44// package act on. Initially, both variables point to the same Registry, which
45// has a process collector (currently on Linux only, see NewProcessCollector)
46// and a Go collector (see NewGoCollector, in particular the note about
47// stop-the-world implication with Go versions older than 1.9) already
48// registered. This approach to keep default instances as global state mirrors
49// the approach of other packages in the Go standard library. Note that there
50// are caveats. Change the variables with caution and only if you understand the
51// consequences. Users who want to avoid global state altogether should not use
52// the convenience functions and act on custom instances instead.
53var (
54 defaultRegistry = NewRegistry()
55 DefaultRegisterer Registerer = defaultRegistry
56 DefaultGatherer Gatherer = defaultRegistry
57)
58
59func init() {
60 MustRegister(NewProcessCollector(ProcessCollectorOpts{}))
61 MustRegister(NewGoCollector())
62}
63
64// NewRegistry creates a new vanilla Registry without any Collectors
65// pre-registered.
66func NewRegistry() *Registry {
67 return &Registry{
68 collectorsByID: map[uint64]Collector{},
69 descIDs: map[uint64]struct{}{},
70 dimHashesByName: map[string]uint64{},
71 }
72}
73
74// NewPedanticRegistry returns a registry that checks during collection if each
75// collected Metric is consistent with its reported Desc, and if the Desc has
76// actually been registered with the registry. Unchecked Collectors (those whose
77// Describe methed does not yield any descriptors) are excluded from the check.
78//
79// Usually, a Registry will be happy as long as the union of all collected
80// Metrics is consistent and valid even if some metrics are not consistent with
81// their own Desc or a Desc provided by their registered Collector. Well-behaved
82// Collectors and Metrics will only provide consistent Descs. This Registry is
83// useful to test the implementation of Collectors and Metrics.
84func NewPedanticRegistry() *Registry {
85 r := NewRegistry()
86 r.pedanticChecksEnabled = true
87 return r
88}
89
90// Registerer is the interface for the part of a registry in charge of
91// registering and unregistering. Users of custom registries should use
92// Registerer as type for registration purposes (rather than the Registry type
93// directly). In that way, they are free to use custom Registerer implementation
94// (e.g. for testing purposes).
95type Registerer interface {
96 // Register registers a new Collector to be included in metrics
97 // collection. It returns an error if the descriptors provided by the
98 // Collector are invalid or if they — in combination with descriptors of
99 // already registered Collectors — do not fulfill the consistency and
100 // uniqueness criteria described in the documentation of metric.Desc.
101 //
102 // If the provided Collector is equal to a Collector already registered
103 // (which includes the case of re-registering the same Collector), the
104 // returned error is an instance of AlreadyRegisteredError, which
105 // contains the previously registered Collector.
106 //
107 // A Collector whose Describe method does not yield any Desc is treated
108 // as unchecked. Registration will always succeed. No check for
109 // re-registering (see previous paragraph) is performed. Thus, the
110 // caller is responsible for not double-registering the same unchecked
111 // Collector, and for providing a Collector that will not cause
112 // inconsistent metrics on collection. (This would lead to scrape
113 // errors.)
114 Register(Collector) error
115 // MustRegister works like Register but registers any number of
116 // Collectors and panics upon the first registration that causes an
117 // error.
118 MustRegister(...Collector)
119 // Unregister unregisters the Collector that equals the Collector passed
120 // in as an argument. (Two Collectors are considered equal if their
121 // Describe method yields the same set of descriptors.) The function
122 // returns whether a Collector was unregistered. Note that an unchecked
123 // Collector cannot be unregistered (as its Describe method does not
124 // yield any descriptor).
125 //
126 // Note that even after unregistering, it will not be possible to
127 // register a new Collector that is inconsistent with the unregistered
128 // Collector, e.g. a Collector collecting metrics with the same name but
129 // a different help string. The rationale here is that the same registry
130 // instance must only collect consistent metrics throughout its
131 // lifetime.
132 Unregister(Collector) bool
133}
134
135// Gatherer is the interface for the part of a registry in charge of gathering
136// the collected metrics into a number of MetricFamilies. The Gatherer interface
137// comes with the same general implication as described for the Registerer
138// interface.
139type Gatherer interface {
140 // Gather calls the Collect method of the registered Collectors and then
141 // gathers the collected metrics into a lexicographically sorted slice
142 // of uniquely named MetricFamily protobufs. Gather ensures that the
143 // returned slice is valid and self-consistent so that it can be used
144 // for valid exposition. As an exception to the strict consistency
145 // requirements described for metric.Desc, Gather will tolerate
146 // different sets of label names for metrics of the same metric family.
147 //
148 // Even if an error occurs, Gather attempts to gather as many metrics as
149 // possible. Hence, if a non-nil error is returned, the returned
150 // MetricFamily slice could be nil (in case of a fatal error that
151 // prevented any meaningful metric collection) or contain a number of
152 // MetricFamily protobufs, some of which might be incomplete, and some
153 // might be missing altogether. The returned error (which might be a
154 // MultiError) explains the details. Note that this is mostly useful for
155 // debugging purposes. If the gathered protobufs are to be used for
156 // exposition in actual monitoring, it is almost always better to not
157 // expose an incomplete result and instead disregard the returned
158 // MetricFamily protobufs in case the returned error is non-nil.
159 Gather() ([]*dto.MetricFamily, error)
160}
161
162// Register registers the provided Collector with the DefaultRegisterer.
163//
164// Register is a shortcut for DefaultRegisterer.Register(c). See there for more
165// details.
166func Register(c Collector) error {
167 return DefaultRegisterer.Register(c)
168}
169
170// MustRegister registers the provided Collectors with the DefaultRegisterer and
171// panics if any error occurs.
172//
173// MustRegister is a shortcut for DefaultRegisterer.MustRegister(cs...). See
174// there for more details.
175func MustRegister(cs ...Collector) {
176 DefaultRegisterer.MustRegister(cs...)
177}
178
179// Unregister removes the registration of the provided Collector from the
180// DefaultRegisterer.
181//
182// Unregister is a shortcut for DefaultRegisterer.Unregister(c). See there for
183// more details.
184func Unregister(c Collector) bool {
185 return DefaultRegisterer.Unregister(c)
186}
187
188// GathererFunc turns a function into a Gatherer.
189type GathererFunc func() ([]*dto.MetricFamily, error)
190
191// Gather implements Gatherer.
192func (gf GathererFunc) Gather() ([]*dto.MetricFamily, error) {
193 return gf()
194}
195
196// AlreadyRegisteredError is returned by the Register method if the Collector to
197// be registered has already been registered before, or a different Collector
198// that collects the same metrics has been registered before. Registration fails
199// in that case, but you can detect from the kind of error what has
200// happened. The error contains fields for the existing Collector and the
201// (rejected) new Collector that equals the existing one. This can be used to
202// find out if an equal Collector has been registered before and switch over to
203// using the old one, as demonstrated in the example.
204type AlreadyRegisteredError struct {
205 ExistingCollector, NewCollector Collector
206}
207
208func (err AlreadyRegisteredError) Error() string {
209 return "duplicate metrics collector registration attempted"
210}
211
212// MultiError is a slice of errors implementing the error interface. It is used
213// by a Gatherer to report multiple errors during MetricFamily gathering.
214type MultiError []error
215
216func (errs MultiError) Error() string {
217 if len(errs) == 0 {
218 return ""
219 }
220 buf := &bytes.Buffer{}
221 fmt.Fprintf(buf, "%d error(s) occurred:", len(errs))
222 for _, err := range errs {
223 fmt.Fprintf(buf, "\n* %s", err)
224 }
225 return buf.String()
226}
227
228// Append appends the provided error if it is not nil.
229func (errs *MultiError) Append(err error) {
230 if err != nil {
231 *errs = append(*errs, err)
232 }
233}
234
235// MaybeUnwrap returns nil if len(errs) is 0. It returns the first and only
236// contained error as error if len(errs is 1). In all other cases, it returns
237// the MultiError directly. This is helpful for returning a MultiError in a way
238// that only uses the MultiError if needed.
239func (errs MultiError) MaybeUnwrap() error {
240 switch len(errs) {
241 case 0:
242 return nil
243 case 1:
244 return errs[0]
245 default:
246 return errs
247 }
248}
249
250// Registry registers Prometheus collectors, collects their metrics, and gathers
251// them into MetricFamilies for exposition. It implements both Registerer and
252// Gatherer. The zero value is not usable. Create instances with NewRegistry or
253// NewPedanticRegistry.
254type Registry struct {
255 mtx sync.RWMutex
256 collectorsByID map[uint64]Collector // ID is a hash of the descIDs.
257 descIDs map[uint64]struct{}
258 dimHashesByName map[string]uint64
259 uncheckedCollectors []Collector
260 pedanticChecksEnabled bool
261}
262
263// Register implements Registerer.
264func (r *Registry) Register(c Collector) error {
265 var (
266 descChan = make(chan *Desc, capDescChan)
267 newDescIDs = map[uint64]struct{}{}
268 newDimHashesByName = map[string]uint64{}
269 collectorID uint64 // Just a sum of all desc IDs.
270 duplicateDescErr error
271 )
272 go func() {
273 c.Describe(descChan)
274 close(descChan)
275 }()
276 r.mtx.Lock()
277 defer func() {
278 // Drain channel in case of premature return to not leak a goroutine.
279 for range descChan {
280 }
281 r.mtx.Unlock()
282 }()
283 // Conduct various tests...
284 for desc := range descChan {
285
286 // Is the descriptor valid at all?
287 if desc.err != nil {
288 return fmt.Errorf("descriptor %s is invalid: %s", desc, desc.err)
289 }
290
291 // Is the descID unique?
292 // (In other words: Is the fqName + constLabel combination unique?)
293 if _, exists := r.descIDs[desc.id]; exists {
294 duplicateDescErr = fmt.Errorf("descriptor %s already exists with the same fully-qualified name and const label values", desc)
295 }
296 // If it is not a duplicate desc in this collector, add it to
297 // the collectorID. (We allow duplicate descs within the same
298 // collector, but their existence must be a no-op.)
299 if _, exists := newDescIDs[desc.id]; !exists {
300 newDescIDs[desc.id] = struct{}{}
301 collectorID += desc.id
302 }
303
304 // Are all the label names and the help string consistent with
305 // previous descriptors of the same name?
306 // First check existing descriptors...
307 if dimHash, exists := r.dimHashesByName[desc.fqName]; exists {
308 if dimHash != desc.dimHash {
309 return fmt.Errorf("a previously registered descriptor with the same fully-qualified name as %s has different label names or a different help string", desc)
310 }
311 } else {
312 // ...then check the new descriptors already seen.
313 if dimHash, exists := newDimHashesByName[desc.fqName]; exists {
314 if dimHash != desc.dimHash {
315 return fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc)
316 }
317 } else {
318 newDimHashesByName[desc.fqName] = desc.dimHash
319 }
320 }
321 }
322 // A Collector yielding no Desc at all is considered unchecked.
323 if len(newDescIDs) == 0 {
324 r.uncheckedCollectors = append(r.uncheckedCollectors, c)
325 return nil
326 }
327 if existing, exists := r.collectorsByID[collectorID]; exists {
328 return AlreadyRegisteredError{
329 ExistingCollector: existing,
330 NewCollector: c,
331 }
332 }
333 // If the collectorID is new, but at least one of the descs existed
334 // before, we are in trouble.
335 if duplicateDescErr != nil {
336 return duplicateDescErr
337 }
338
339 // Only after all tests have passed, actually register.
340 r.collectorsByID[collectorID] = c
341 for hash := range newDescIDs {
342 r.descIDs[hash] = struct{}{}
343 }
344 for name, dimHash := range newDimHashesByName {
345 r.dimHashesByName[name] = dimHash
346 }
347 return nil
348}
349
350// Unregister implements Registerer.
351func (r *Registry) Unregister(c Collector) bool {
352 var (
353 descChan = make(chan *Desc, capDescChan)
354 descIDs = map[uint64]struct{}{}
355 collectorID uint64 // Just a sum of the desc IDs.
356 )
357 go func() {
358 c.Describe(descChan)
359 close(descChan)
360 }()
361 for desc := range descChan {
362 if _, exists := descIDs[desc.id]; !exists {
363 collectorID += desc.id
364 descIDs[desc.id] = struct{}{}
365 }
366 }
367
368 r.mtx.RLock()
369 if _, exists := r.collectorsByID[collectorID]; !exists {
370 r.mtx.RUnlock()
371 return false
372 }
373 r.mtx.RUnlock()
374
375 r.mtx.Lock()
376 defer r.mtx.Unlock()
377
378 delete(r.collectorsByID, collectorID)
379 for id := range descIDs {
380 delete(r.descIDs, id)
381 }
382 // dimHashesByName is left untouched as those must be consistent
383 // throughout the lifetime of a program.
384 return true
385}
386
387// MustRegister implements Registerer.
388func (r *Registry) MustRegister(cs ...Collector) {
389 for _, c := range cs {
390 if err := r.Register(c); err != nil {
391 panic(err)
392 }
393 }
394}
395
396// Gather implements Gatherer.
397func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
398 var (
399 checkedMetricChan = make(chan Metric, capMetricChan)
400 uncheckedMetricChan = make(chan Metric, capMetricChan)
401 metricHashes = map[uint64]struct{}{}
402 wg sync.WaitGroup
403 errs MultiError // The collected errors to return in the end.
404 registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
405 )
406
407 r.mtx.RLock()
408 goroutineBudget := len(r.collectorsByID) + len(r.uncheckedCollectors)
409 metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
410 checkedCollectors := make(chan Collector, len(r.collectorsByID))
411 uncheckedCollectors := make(chan Collector, len(r.uncheckedCollectors))
412 for _, collector := range r.collectorsByID {
413 checkedCollectors <- collector
414 }
415 for _, collector := range r.uncheckedCollectors {
416 uncheckedCollectors <- collector
417 }
418 // In case pedantic checks are enabled, we have to copy the map before
419 // giving up the RLock.
420 if r.pedanticChecksEnabled {
421 registeredDescIDs = make(map[uint64]struct{}, len(r.descIDs))
422 for id := range r.descIDs {
423 registeredDescIDs[id] = struct{}{}
424 }
425 }
426 r.mtx.RUnlock()
427
428 wg.Add(goroutineBudget)
429
430 collectWorker := func() {
431 for {
432 select {
433 case collector := <-checkedCollectors:
434 collector.Collect(checkedMetricChan)
435 case collector := <-uncheckedCollectors:
436 collector.Collect(uncheckedMetricChan)
437 default:
438 return
439 }
440 wg.Done()
441 }
442 }
443
444 // Start the first worker now to make sure at least one is running.
445 go collectWorker()
446 goroutineBudget--
447
448 // Close checkedMetricChan and uncheckedMetricChan once all collectors
449 // are collected.
450 go func() {
451 wg.Wait()
452 close(checkedMetricChan)
453 close(uncheckedMetricChan)
454 }()
455
456 // Drain checkedMetricChan and uncheckedMetricChan in case of premature return.
457 defer func() {
458 if checkedMetricChan != nil {
459 for range checkedMetricChan {
460 }
461 }
462 if uncheckedMetricChan != nil {
463 for range uncheckedMetricChan {
464 }
465 }
466 }()
467
468 // Copy the channel references so we can nil them out later to remove
469 // them from the select statements below.
470 cmc := checkedMetricChan
471 umc := uncheckedMetricChan
472
473 for {
474 select {
475 case metric, ok := <-cmc:
476 if !ok {
477 cmc = nil
478 break
479 }
480 errs.Append(processMetric(
481 metric, metricFamiliesByName,
482 metricHashes,
483 registeredDescIDs,
484 ))
485 case metric, ok := <-umc:
486 if !ok {
487 umc = nil
488 break
489 }
490 errs.Append(processMetric(
491 metric, metricFamiliesByName,
492 metricHashes,
493 nil,
494 ))
495 default:
496 if goroutineBudget <= 0 || len(checkedCollectors)+len(uncheckedCollectors) == 0 {
497 // All collectors are already being worked on or
498 // we have already as many goroutines started as
499 // there are collectors. Do the same as above,
500 // just without the default.
501 select {
502 case metric, ok := <-cmc:
503 if !ok {
504 cmc = nil
505 break
506 }
507 errs.Append(processMetric(
508 metric, metricFamiliesByName,
509 metricHashes,
510 registeredDescIDs,
511 ))
512 case metric, ok := <-umc:
513 if !ok {
514 umc = nil
515 break
516 }
517 errs.Append(processMetric(
518 metric, metricFamiliesByName,
519 metricHashes,
520 nil,
521 ))
522 }
523 break
524 }
525 // Start more workers.
526 go collectWorker()
527 goroutineBudget--
528 runtime.Gosched()
529 }
530 // Once both checkedMetricChan and uncheckdMetricChan are closed
531 // and drained, the contraption above will nil out cmc and umc,
532 // and then we can leave the collect loop here.
533 if cmc == nil && umc == nil {
534 break
535 }
536 }
537 return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
538}
539
540// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the
541// Prometheus text format, and writes it to a temporary file. Upon success, the
542// temporary file is renamed to the provided filename.
543//
544// This is intended for use with the textfile collector of the node exporter.
545// Note that the node exporter expects the filename to be suffixed with ".prom".
546func WriteToTextfile(filename string, g Gatherer) error {
547 tmp, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
548 if err != nil {
549 return err
550 }
551 defer os.Remove(tmp.Name())
552
553 mfs, err := g.Gather()
554 if err != nil {
555 return err
556 }
557 for _, mf := range mfs {
558 if _, err := expfmt.MetricFamilyToText(tmp, mf); err != nil {
559 return err
560 }
561 }
562 if err := tmp.Close(); err != nil {
563 return err
564 }
565
566 if err := os.Chmod(tmp.Name(), 0644); err != nil {
567 return err
568 }
569 return os.Rename(tmp.Name(), filename)
570}
571
572// processMetric is an internal helper method only used by the Gather method.
573func processMetric(
574 metric Metric,
575 metricFamiliesByName map[string]*dto.MetricFamily,
576 metricHashes map[uint64]struct{},
577 registeredDescIDs map[uint64]struct{},
578) error {
579 desc := metric.Desc()
580 // Wrapped metrics collected by an unchecked Collector can have an
581 // invalid Desc.
582 if desc.err != nil {
583 return desc.err
584 }
585 dtoMetric := &dto.Metric{}
586 if err := metric.Write(dtoMetric); err != nil {
587 return fmt.Errorf("error collecting metric %v: %s", desc, err)
588 }
589 metricFamily, ok := metricFamiliesByName[desc.fqName]
590 if ok { // Existing name.
591 if metricFamily.GetHelp() != desc.help {
592 return fmt.Errorf(
593 "collected metric %s %s has help %q but should have %q",
594 desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
595 )
596 }
597 // TODO(beorn7): Simplify switch once Desc has type.
598 switch metricFamily.GetType() {
599 case dto.MetricType_COUNTER:
600 if dtoMetric.Counter == nil {
601 return fmt.Errorf(
602 "collected metric %s %s should be a Counter",
603 desc.fqName, dtoMetric,
604 )
605 }
606 case dto.MetricType_GAUGE:
607 if dtoMetric.Gauge == nil {
608 return fmt.Errorf(
609 "collected metric %s %s should be a Gauge",
610 desc.fqName, dtoMetric,
611 )
612 }
613 case dto.MetricType_SUMMARY:
614 if dtoMetric.Summary == nil {
615 return fmt.Errorf(
616 "collected metric %s %s should be a Summary",
617 desc.fqName, dtoMetric,
618 )
619 }
620 case dto.MetricType_UNTYPED:
621 if dtoMetric.Untyped == nil {
622 return fmt.Errorf(
623 "collected metric %s %s should be Untyped",
624 desc.fqName, dtoMetric,
625 )
626 }
627 case dto.MetricType_HISTOGRAM:
628 if dtoMetric.Histogram == nil {
629 return fmt.Errorf(
630 "collected metric %s %s should be a Histogram",
631 desc.fqName, dtoMetric,
632 )
633 }
634 default:
635 panic("encountered MetricFamily with invalid type")
636 }
637 } else { // New name.
638 metricFamily = &dto.MetricFamily{}
639 metricFamily.Name = proto.String(desc.fqName)
640 metricFamily.Help = proto.String(desc.help)
641 // TODO(beorn7): Simplify switch once Desc has type.
642 switch {
643 case dtoMetric.Gauge != nil:
644 metricFamily.Type = dto.MetricType_GAUGE.Enum()
645 case dtoMetric.Counter != nil:
646 metricFamily.Type = dto.MetricType_COUNTER.Enum()
647 case dtoMetric.Summary != nil:
648 metricFamily.Type = dto.MetricType_SUMMARY.Enum()
649 case dtoMetric.Untyped != nil:
650 metricFamily.Type = dto.MetricType_UNTYPED.Enum()
651 case dtoMetric.Histogram != nil:
652 metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
653 default:
654 return fmt.Errorf("empty metric collected: %s", dtoMetric)
655 }
656 if err := checkSuffixCollisions(metricFamily, metricFamiliesByName); err != nil {
657 return err
658 }
659 metricFamiliesByName[desc.fqName] = metricFamily
660 }
661 if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes); err != nil {
662 return err
663 }
664 if registeredDescIDs != nil {
665 // Is the desc registered at all?
666 if _, exist := registeredDescIDs[desc.id]; !exist {
667 return fmt.Errorf(
668 "collected metric %s %s with unregistered descriptor %s",
669 metricFamily.GetName(), dtoMetric, desc,
670 )
671 }
672 if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
673 return err
674 }
675 }
676 metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
677 return nil
678}
679
680// Gatherers is a slice of Gatherer instances that implements the Gatherer
681// interface itself. Its Gather method calls Gather on all Gatherers in the
682// slice in order and returns the merged results. Errors returned from the
683// Gather calles are all returned in a flattened MultiError. Duplicate and
684// inconsistent Metrics are skipped (first occurrence in slice order wins) and
685// reported in the returned error.
686//
687// Gatherers can be used to merge the Gather results from multiple
688// Registries. It also provides a way to directly inject existing MetricFamily
689// protobufs into the gathering by creating a custom Gatherer with a Gather
690// method that simply returns the existing MetricFamily protobufs. Note that no
691// registration is involved (in contrast to Collector registration), so
692// obviously registration-time checks cannot happen. Any inconsistencies between
693// the gathered MetricFamilies are reported as errors by the Gather method, and
694// inconsistent Metrics are dropped. Invalid parts of the MetricFamilies
695// (e.g. syntactically invalid metric or label names) will go undetected.
696type Gatherers []Gatherer
697
698// Gather implements Gatherer.
699func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
700 var (
701 metricFamiliesByName = map[string]*dto.MetricFamily{}
702 metricHashes = map[uint64]struct{}{}
703 errs MultiError // The collected errors to return in the end.
704 )
705
706 for i, g := range gs {
707 mfs, err := g.Gather()
708 if err != nil {
709 if multiErr, ok := err.(MultiError); ok {
710 for _, err := range multiErr {
711 errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
712 }
713 } else {
714 errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
715 }
716 }
717 for _, mf := range mfs {
718 existingMF, exists := metricFamiliesByName[mf.GetName()]
719 if exists {
720 if existingMF.GetHelp() != mf.GetHelp() {
721 errs = append(errs, fmt.Errorf(
722 "gathered metric family %s has help %q but should have %q",
723 mf.GetName(), mf.GetHelp(), existingMF.GetHelp(),
724 ))
725 continue
726 }
727 if existingMF.GetType() != mf.GetType() {
728 errs = append(errs, fmt.Errorf(
729 "gathered metric family %s has type %s but should have %s",
730 mf.GetName(), mf.GetType(), existingMF.GetType(),
731 ))
732 continue
733 }
734 } else {
735 existingMF = &dto.MetricFamily{}
736 existingMF.Name = mf.Name
737 existingMF.Help = mf.Help
738 existingMF.Type = mf.Type
739 if err := checkSuffixCollisions(existingMF, metricFamiliesByName); err != nil {
740 errs = append(errs, err)
741 continue
742 }
743 metricFamiliesByName[mf.GetName()] = existingMF
744 }
745 for _, m := range mf.Metric {
746 if err := checkMetricConsistency(existingMF, m, metricHashes); err != nil {
747 errs = append(errs, err)
748 continue
749 }
750 existingMF.Metric = append(existingMF.Metric, m)
751 }
752 }
753 }
754 return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
755}
756
757// checkSuffixCollisions checks for collisions with the “magic” suffixes the
758// Prometheus text format and the internal metric representation of the
759// Prometheus server add while flattening Summaries and Histograms.
760func checkSuffixCollisions(mf *dto.MetricFamily, mfs map[string]*dto.MetricFamily) error {
761 var (
762 newName = mf.GetName()
763 newType = mf.GetType()
764 newNameWithoutSuffix = ""
765 )
766 switch {
767 case strings.HasSuffix(newName, "_count"):
768 newNameWithoutSuffix = newName[:len(newName)-6]
769 case strings.HasSuffix(newName, "_sum"):
770 newNameWithoutSuffix = newName[:len(newName)-4]
771 case strings.HasSuffix(newName, "_bucket"):
772 newNameWithoutSuffix = newName[:len(newName)-7]
773 }
774 if newNameWithoutSuffix != "" {
775 if existingMF, ok := mfs[newNameWithoutSuffix]; ok {
776 switch existingMF.GetType() {
777 case dto.MetricType_SUMMARY:
778 if !strings.HasSuffix(newName, "_bucket") {
779 return fmt.Errorf(
780 "collected metric named %q collides with previously collected summary named %q",
781 newName, newNameWithoutSuffix,
782 )
783 }
784 case dto.MetricType_HISTOGRAM:
785 return fmt.Errorf(
786 "collected metric named %q collides with previously collected histogram named %q",
787 newName, newNameWithoutSuffix,
788 )
789 }
790 }
791 }
792 if newType == dto.MetricType_SUMMARY || newType == dto.MetricType_HISTOGRAM {
793 if _, ok := mfs[newName+"_count"]; ok {
794 return fmt.Errorf(
795 "collected histogram or summary named %q collides with previously collected metric named %q",
796 newName, newName+"_count",
797 )
798 }
799 if _, ok := mfs[newName+"_sum"]; ok {
800 return fmt.Errorf(
801 "collected histogram or summary named %q collides with previously collected metric named %q",
802 newName, newName+"_sum",
803 )
804 }
805 }
806 if newType == dto.MetricType_HISTOGRAM {
807 if _, ok := mfs[newName+"_bucket"]; ok {
808 return fmt.Errorf(
809 "collected histogram named %q collides with previously collected metric named %q",
810 newName, newName+"_bucket",
811 )
812 }
813 }
814 return nil
815}
816
817// checkMetricConsistency checks if the provided Metric is consistent with the
818// provided MetricFamily. It also hashes the Metric labels and the MetricFamily
819// name. If the resulting hash is already in the provided metricHashes, an error
820// is returned. If not, it is added to metricHashes.
821func checkMetricConsistency(
822 metricFamily *dto.MetricFamily,
823 dtoMetric *dto.Metric,
824 metricHashes map[uint64]struct{},
825) error {
826 name := metricFamily.GetName()
827
828 // Type consistency with metric family.
829 if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
830 metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
831 metricFamily.GetType() == dto.MetricType_SUMMARY && dtoMetric.Summary == nil ||
832 metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil ||
833 metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil {
834 return fmt.Errorf(
835 "collected metric %q { %s} is not a %s",
836 name, dtoMetric, metricFamily.GetType(),
837 )
838 }
839
840 previousLabelName := ""
841 for _, labelPair := range dtoMetric.GetLabel() {
842 labelName := labelPair.GetName()
843 if labelName == previousLabelName {
844 return fmt.Errorf(
845 "collected metric %q { %s} has two or more labels with the same name: %s",
846 name, dtoMetric, labelName,
847 )
848 }
849 if !checkLabelName(labelName) {
850 return fmt.Errorf(
851 "collected metric %q { %s} has a label with an invalid name: %s",
852 name, dtoMetric, labelName,
853 )
854 }
855 if dtoMetric.Summary != nil && labelName == quantileLabel {
856 return fmt.Errorf(
857 "collected metric %q { %s} must not have an explicit %q label",
858 name, dtoMetric, quantileLabel,
859 )
860 }
861 if !utf8.ValidString(labelPair.GetValue()) {
862 return fmt.Errorf(
863 "collected metric %q { %s} has a label named %q whose value is not utf8: %#v",
864 name, dtoMetric, labelName, labelPair.GetValue())
865 }
866 previousLabelName = labelName
867 }
868
869 // Is the metric unique (i.e. no other metric with the same name and the same labels)?
870 h := hashNew()
871 h = hashAdd(h, name)
872 h = hashAddByte(h, separatorByte)
873 // Make sure label pairs are sorted. We depend on it for the consistency
874 // check.
875 if !sort.IsSorted(labelPairSorter(dtoMetric.Label)) {
876 // We cannot sort dtoMetric.Label in place as it is immutable by contract.
877 copiedLabels := make([]*dto.LabelPair, len(dtoMetric.Label))
878 copy(copiedLabels, dtoMetric.Label)
879 sort.Sort(labelPairSorter(copiedLabels))
880 dtoMetric.Label = copiedLabels
881 }
882 for _, lp := range dtoMetric.Label {
883 h = hashAdd(h, lp.GetName())
884 h = hashAddByte(h, separatorByte)
885 h = hashAdd(h, lp.GetValue())
886 h = hashAddByte(h, separatorByte)
887 }
888 if _, exists := metricHashes[h]; exists {
889 return fmt.Errorf(
890 "collected metric %q { %s} was collected before with the same name and label values",
891 name, dtoMetric,
892 )
893 }
894 metricHashes[h] = struct{}{}
895 return nil
896}
897
898func checkDescConsistency(
899 metricFamily *dto.MetricFamily,
900 dtoMetric *dto.Metric,
901 desc *Desc,
902) error {
903 // Desc help consistency with metric family help.
904 if metricFamily.GetHelp() != desc.help {
905 return fmt.Errorf(
906 "collected metric %s %s has help %q but should have %q",
907 metricFamily.GetName(), dtoMetric, metricFamily.GetHelp(), desc.help,
908 )
909 }
910
911 // Is the desc consistent with the content of the metric?
912 lpsFromDesc := make([]*dto.LabelPair, len(desc.constLabelPairs), len(dtoMetric.Label))
913 copy(lpsFromDesc, desc.constLabelPairs)
914 for _, l := range desc.variableLabels {
915 lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
916 Name: proto.String(l),
917 })
918 }
919 if len(lpsFromDesc) != len(dtoMetric.Label) {
920 return fmt.Errorf(
921 "labels in collected metric %s %s are inconsistent with descriptor %s",
922 metricFamily.GetName(), dtoMetric, desc,
923 )
924 }
925 sort.Sort(labelPairSorter(lpsFromDesc))
926 for i, lpFromDesc := range lpsFromDesc {
927 lpFromMetric := dtoMetric.Label[i]
928 if lpFromDesc.GetName() != lpFromMetric.GetName() ||
929 lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
930 return fmt.Errorf(
931 "labels in collected metric %s %s are inconsistent with descriptor %s",
932 metricFamily.GetName(), dtoMetric, desc,
933 )
934 }
935 }
936 return nil
937}