blob: 6c32516aa2e312115fcfc4da395f51d3171c73ea [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -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 switch e := existing.(type) {
329 case *wrappingCollector:
330 return AlreadyRegisteredError{
331 ExistingCollector: e.unwrapRecursively(),
332 NewCollector: c,
333 }
334 default:
335 return AlreadyRegisteredError{
336 ExistingCollector: e,
337 NewCollector: c,
338 }
339 }
340 }
341 // If the collectorID is new, but at least one of the descs existed
342 // before, we are in trouble.
343 if duplicateDescErr != nil {
344 return duplicateDescErr
345 }
346
347 // Only after all tests have passed, actually register.
348 r.collectorsByID[collectorID] = c
349 for hash := range newDescIDs {
350 r.descIDs[hash] = struct{}{}
351 }
352 for name, dimHash := range newDimHashesByName {
353 r.dimHashesByName[name] = dimHash
354 }
355 return nil
356}
357
358// Unregister implements Registerer.
359func (r *Registry) Unregister(c Collector) bool {
360 var (
361 descChan = make(chan *Desc, capDescChan)
362 descIDs = map[uint64]struct{}{}
363 collectorID uint64 // Just a sum of the desc IDs.
364 )
365 go func() {
366 c.Describe(descChan)
367 close(descChan)
368 }()
369 for desc := range descChan {
370 if _, exists := descIDs[desc.id]; !exists {
371 collectorID += desc.id
372 descIDs[desc.id] = struct{}{}
373 }
374 }
375
376 r.mtx.RLock()
377 if _, exists := r.collectorsByID[collectorID]; !exists {
378 r.mtx.RUnlock()
379 return false
380 }
381 r.mtx.RUnlock()
382
383 r.mtx.Lock()
384 defer r.mtx.Unlock()
385
386 delete(r.collectorsByID, collectorID)
387 for id := range descIDs {
388 delete(r.descIDs, id)
389 }
390 // dimHashesByName is left untouched as those must be consistent
391 // throughout the lifetime of a program.
392 return true
393}
394
395// MustRegister implements Registerer.
396func (r *Registry) MustRegister(cs ...Collector) {
397 for _, c := range cs {
398 if err := r.Register(c); err != nil {
399 panic(err)
400 }
401 }
402}
403
404// Gather implements Gatherer.
405func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
406 var (
407 checkedMetricChan = make(chan Metric, capMetricChan)
408 uncheckedMetricChan = make(chan Metric, capMetricChan)
409 metricHashes = map[uint64]struct{}{}
410 wg sync.WaitGroup
411 errs MultiError // The collected errors to return in the end.
412 registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
413 )
414
415 r.mtx.RLock()
416 goroutineBudget := len(r.collectorsByID) + len(r.uncheckedCollectors)
417 metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
418 checkedCollectors := make(chan Collector, len(r.collectorsByID))
419 uncheckedCollectors := make(chan Collector, len(r.uncheckedCollectors))
420 for _, collector := range r.collectorsByID {
421 checkedCollectors <- collector
422 }
423 for _, collector := range r.uncheckedCollectors {
424 uncheckedCollectors <- collector
425 }
426 // In case pedantic checks are enabled, we have to copy the map before
427 // giving up the RLock.
428 if r.pedanticChecksEnabled {
429 registeredDescIDs = make(map[uint64]struct{}, len(r.descIDs))
430 for id := range r.descIDs {
431 registeredDescIDs[id] = struct{}{}
432 }
433 }
434 r.mtx.RUnlock()
435
436 wg.Add(goroutineBudget)
437
438 collectWorker := func() {
439 for {
440 select {
441 case collector := <-checkedCollectors:
442 collector.Collect(checkedMetricChan)
443 case collector := <-uncheckedCollectors:
444 collector.Collect(uncheckedMetricChan)
445 default:
446 return
447 }
448 wg.Done()
449 }
450 }
451
452 // Start the first worker now to make sure at least one is running.
453 go collectWorker()
454 goroutineBudget--
455
456 // Close checkedMetricChan and uncheckedMetricChan once all collectors
457 // are collected.
458 go func() {
459 wg.Wait()
460 close(checkedMetricChan)
461 close(uncheckedMetricChan)
462 }()
463
464 // Drain checkedMetricChan and uncheckedMetricChan in case of premature return.
465 defer func() {
466 if checkedMetricChan != nil {
467 for range checkedMetricChan {
468 }
469 }
470 if uncheckedMetricChan != nil {
471 for range uncheckedMetricChan {
472 }
473 }
474 }()
475
476 // Copy the channel references so we can nil them out later to remove
477 // them from the select statements below.
478 cmc := checkedMetricChan
479 umc := uncheckedMetricChan
480
481 for {
482 select {
483 case metric, ok := <-cmc:
484 if !ok {
485 cmc = nil
486 break
487 }
488 errs.Append(processMetric(
489 metric, metricFamiliesByName,
490 metricHashes,
491 registeredDescIDs,
492 ))
493 case metric, ok := <-umc:
494 if !ok {
495 umc = nil
496 break
497 }
498 errs.Append(processMetric(
499 metric, metricFamiliesByName,
500 metricHashes,
501 nil,
502 ))
503 default:
504 if goroutineBudget <= 0 || len(checkedCollectors)+len(uncheckedCollectors) == 0 {
505 // All collectors are already being worked on or
506 // we have already as many goroutines started as
507 // there are collectors. Do the same as above,
508 // just without the default.
509 select {
510 case metric, ok := <-cmc:
511 if !ok {
512 cmc = nil
513 break
514 }
515 errs.Append(processMetric(
516 metric, metricFamiliesByName,
517 metricHashes,
518 registeredDescIDs,
519 ))
520 case metric, ok := <-umc:
521 if !ok {
522 umc = nil
523 break
524 }
525 errs.Append(processMetric(
526 metric, metricFamiliesByName,
527 metricHashes,
528 nil,
529 ))
530 }
531 break
532 }
533 // Start more workers.
534 go collectWorker()
535 goroutineBudget--
536 runtime.Gosched()
537 }
538 // Once both checkedMetricChan and uncheckdMetricChan are closed
539 // and drained, the contraption above will nil out cmc and umc,
540 // and then we can leave the collect loop here.
541 if cmc == nil && umc == nil {
542 break
543 }
544 }
545 return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
546}
547
548// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the
549// Prometheus text format, and writes it to a temporary file. Upon success, the
550// temporary file is renamed to the provided filename.
551//
552// This is intended for use with the textfile collector of the node exporter.
553// Note that the node exporter expects the filename to be suffixed with ".prom".
554func WriteToTextfile(filename string, g Gatherer) error {
555 tmp, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
556 if err != nil {
557 return err
558 }
559 defer os.Remove(tmp.Name())
560
561 mfs, err := g.Gather()
562 if err != nil {
563 return err
564 }
565 for _, mf := range mfs {
566 if _, err := expfmt.MetricFamilyToText(tmp, mf); err != nil {
567 return err
568 }
569 }
570 if err := tmp.Close(); err != nil {
571 return err
572 }
573
574 if err := os.Chmod(tmp.Name(), 0644); err != nil {
575 return err
576 }
577 return os.Rename(tmp.Name(), filename)
578}
579
580// processMetric is an internal helper method only used by the Gather method.
581func processMetric(
582 metric Metric,
583 metricFamiliesByName map[string]*dto.MetricFamily,
584 metricHashes map[uint64]struct{},
585 registeredDescIDs map[uint64]struct{},
586) error {
587 desc := metric.Desc()
588 // Wrapped metrics collected by an unchecked Collector can have an
589 // invalid Desc.
590 if desc.err != nil {
591 return desc.err
592 }
593 dtoMetric := &dto.Metric{}
594 if err := metric.Write(dtoMetric); err != nil {
595 return fmt.Errorf("error collecting metric %v: %s", desc, err)
596 }
597 metricFamily, ok := metricFamiliesByName[desc.fqName]
598 if ok { // Existing name.
599 if metricFamily.GetHelp() != desc.help {
600 return fmt.Errorf(
601 "collected metric %s %s has help %q but should have %q",
602 desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
603 )
604 }
605 // TODO(beorn7): Simplify switch once Desc has type.
606 switch metricFamily.GetType() {
607 case dto.MetricType_COUNTER:
608 if dtoMetric.Counter == nil {
609 return fmt.Errorf(
610 "collected metric %s %s should be a Counter",
611 desc.fqName, dtoMetric,
612 )
613 }
614 case dto.MetricType_GAUGE:
615 if dtoMetric.Gauge == nil {
616 return fmt.Errorf(
617 "collected metric %s %s should be a Gauge",
618 desc.fqName, dtoMetric,
619 )
620 }
621 case dto.MetricType_SUMMARY:
622 if dtoMetric.Summary == nil {
623 return fmt.Errorf(
624 "collected metric %s %s should be a Summary",
625 desc.fqName, dtoMetric,
626 )
627 }
628 case dto.MetricType_UNTYPED:
629 if dtoMetric.Untyped == nil {
630 return fmt.Errorf(
631 "collected metric %s %s should be Untyped",
632 desc.fqName, dtoMetric,
633 )
634 }
635 case dto.MetricType_HISTOGRAM:
636 if dtoMetric.Histogram == nil {
637 return fmt.Errorf(
638 "collected metric %s %s should be a Histogram",
639 desc.fqName, dtoMetric,
640 )
641 }
642 default:
643 panic("encountered MetricFamily with invalid type")
644 }
645 } else { // New name.
646 metricFamily = &dto.MetricFamily{}
647 metricFamily.Name = proto.String(desc.fqName)
648 metricFamily.Help = proto.String(desc.help)
649 // TODO(beorn7): Simplify switch once Desc has type.
650 switch {
651 case dtoMetric.Gauge != nil:
652 metricFamily.Type = dto.MetricType_GAUGE.Enum()
653 case dtoMetric.Counter != nil:
654 metricFamily.Type = dto.MetricType_COUNTER.Enum()
655 case dtoMetric.Summary != nil:
656 metricFamily.Type = dto.MetricType_SUMMARY.Enum()
657 case dtoMetric.Untyped != nil:
658 metricFamily.Type = dto.MetricType_UNTYPED.Enum()
659 case dtoMetric.Histogram != nil:
660 metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
661 default:
662 return fmt.Errorf("empty metric collected: %s", dtoMetric)
663 }
664 if err := checkSuffixCollisions(metricFamily, metricFamiliesByName); err != nil {
665 return err
666 }
667 metricFamiliesByName[desc.fqName] = metricFamily
668 }
669 if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes); err != nil {
670 return err
671 }
672 if registeredDescIDs != nil {
673 // Is the desc registered at all?
674 if _, exist := registeredDescIDs[desc.id]; !exist {
675 return fmt.Errorf(
676 "collected metric %s %s with unregistered descriptor %s",
677 metricFamily.GetName(), dtoMetric, desc,
678 )
679 }
680 if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
681 return err
682 }
683 }
684 metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
685 return nil
686}
687
688// Gatherers is a slice of Gatherer instances that implements the Gatherer
689// interface itself. Its Gather method calls Gather on all Gatherers in the
690// slice in order and returns the merged results. Errors returned from the
691// Gather calls are all returned in a flattened MultiError. Duplicate and
692// inconsistent Metrics are skipped (first occurrence in slice order wins) and
693// reported in the returned error.
694//
695// Gatherers can be used to merge the Gather results from multiple
696// Registries. It also provides a way to directly inject existing MetricFamily
697// protobufs into the gathering by creating a custom Gatherer with a Gather
698// method that simply returns the existing MetricFamily protobufs. Note that no
699// registration is involved (in contrast to Collector registration), so
700// obviously registration-time checks cannot happen. Any inconsistencies between
701// the gathered MetricFamilies are reported as errors by the Gather method, and
702// inconsistent Metrics are dropped. Invalid parts of the MetricFamilies
703// (e.g. syntactically invalid metric or label names) will go undetected.
704type Gatherers []Gatherer
705
706// Gather implements Gatherer.
707func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
708 var (
709 metricFamiliesByName = map[string]*dto.MetricFamily{}
710 metricHashes = map[uint64]struct{}{}
711 errs MultiError // The collected errors to return in the end.
712 )
713
714 for i, g := range gs {
715 mfs, err := g.Gather()
716 if err != nil {
717 if multiErr, ok := err.(MultiError); ok {
718 for _, err := range multiErr {
719 errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
720 }
721 } else {
722 errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
723 }
724 }
725 for _, mf := range mfs {
726 existingMF, exists := metricFamiliesByName[mf.GetName()]
727 if exists {
728 if existingMF.GetHelp() != mf.GetHelp() {
729 errs = append(errs, fmt.Errorf(
730 "gathered metric family %s has help %q but should have %q",
731 mf.GetName(), mf.GetHelp(), existingMF.GetHelp(),
732 ))
733 continue
734 }
735 if existingMF.GetType() != mf.GetType() {
736 errs = append(errs, fmt.Errorf(
737 "gathered metric family %s has type %s but should have %s",
738 mf.GetName(), mf.GetType(), existingMF.GetType(),
739 ))
740 continue
741 }
742 } else {
743 existingMF = &dto.MetricFamily{}
744 existingMF.Name = mf.Name
745 existingMF.Help = mf.Help
746 existingMF.Type = mf.Type
747 if err := checkSuffixCollisions(existingMF, metricFamiliesByName); err != nil {
748 errs = append(errs, err)
749 continue
750 }
751 metricFamiliesByName[mf.GetName()] = existingMF
752 }
753 for _, m := range mf.Metric {
754 if err := checkMetricConsistency(existingMF, m, metricHashes); err != nil {
755 errs = append(errs, err)
756 continue
757 }
758 existingMF.Metric = append(existingMF.Metric, m)
759 }
760 }
761 }
762 return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
763}
764
765// checkSuffixCollisions checks for collisions with the “magic” suffixes the
766// Prometheus text format and the internal metric representation of the
767// Prometheus server add while flattening Summaries and Histograms.
768func checkSuffixCollisions(mf *dto.MetricFamily, mfs map[string]*dto.MetricFamily) error {
769 var (
770 newName = mf.GetName()
771 newType = mf.GetType()
772 newNameWithoutSuffix = ""
773 )
774 switch {
775 case strings.HasSuffix(newName, "_count"):
776 newNameWithoutSuffix = newName[:len(newName)-6]
777 case strings.HasSuffix(newName, "_sum"):
778 newNameWithoutSuffix = newName[:len(newName)-4]
779 case strings.HasSuffix(newName, "_bucket"):
780 newNameWithoutSuffix = newName[:len(newName)-7]
781 }
782 if newNameWithoutSuffix != "" {
783 if existingMF, ok := mfs[newNameWithoutSuffix]; ok {
784 switch existingMF.GetType() {
785 case dto.MetricType_SUMMARY:
786 if !strings.HasSuffix(newName, "_bucket") {
787 return fmt.Errorf(
788 "collected metric named %q collides with previously collected summary named %q",
789 newName, newNameWithoutSuffix,
790 )
791 }
792 case dto.MetricType_HISTOGRAM:
793 return fmt.Errorf(
794 "collected metric named %q collides with previously collected histogram named %q",
795 newName, newNameWithoutSuffix,
796 )
797 }
798 }
799 }
800 if newType == dto.MetricType_SUMMARY || newType == dto.MetricType_HISTOGRAM {
801 if _, ok := mfs[newName+"_count"]; ok {
802 return fmt.Errorf(
803 "collected histogram or summary named %q collides with previously collected metric named %q",
804 newName, newName+"_count",
805 )
806 }
807 if _, ok := mfs[newName+"_sum"]; ok {
808 return fmt.Errorf(
809 "collected histogram or summary named %q collides with previously collected metric named %q",
810 newName, newName+"_sum",
811 )
812 }
813 }
814 if newType == dto.MetricType_HISTOGRAM {
815 if _, ok := mfs[newName+"_bucket"]; ok {
816 return fmt.Errorf(
817 "collected histogram named %q collides with previously collected metric named %q",
818 newName, newName+"_bucket",
819 )
820 }
821 }
822 return nil
823}
824
825// checkMetricConsistency checks if the provided Metric is consistent with the
826// provided MetricFamily. It also hashes the Metric labels and the MetricFamily
827// name. If the resulting hash is already in the provided metricHashes, an error
828// is returned. If not, it is added to metricHashes.
829func checkMetricConsistency(
830 metricFamily *dto.MetricFamily,
831 dtoMetric *dto.Metric,
832 metricHashes map[uint64]struct{},
833) error {
834 name := metricFamily.GetName()
835
836 // Type consistency with metric family.
837 if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
838 metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
839 metricFamily.GetType() == dto.MetricType_SUMMARY && dtoMetric.Summary == nil ||
840 metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil ||
841 metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil {
842 return fmt.Errorf(
843 "collected metric %q { %s} is not a %s",
844 name, dtoMetric, metricFamily.GetType(),
845 )
846 }
847
848 previousLabelName := ""
849 for _, labelPair := range dtoMetric.GetLabel() {
850 labelName := labelPair.GetName()
851 if labelName == previousLabelName {
852 return fmt.Errorf(
853 "collected metric %q { %s} has two or more labels with the same name: %s",
854 name, dtoMetric, labelName,
855 )
856 }
857 if !checkLabelName(labelName) {
858 return fmt.Errorf(
859 "collected metric %q { %s} has a label with an invalid name: %s",
860 name, dtoMetric, labelName,
861 )
862 }
863 if dtoMetric.Summary != nil && labelName == quantileLabel {
864 return fmt.Errorf(
865 "collected metric %q { %s} must not have an explicit %q label",
866 name, dtoMetric, quantileLabel,
867 )
868 }
869 if !utf8.ValidString(labelPair.GetValue()) {
870 return fmt.Errorf(
871 "collected metric %q { %s} has a label named %q whose value is not utf8: %#v",
872 name, dtoMetric, labelName, labelPair.GetValue())
873 }
874 previousLabelName = labelName
875 }
876
877 // Is the metric unique (i.e. no other metric with the same name and the same labels)?
878 h := hashNew()
879 h = hashAdd(h, name)
880 h = hashAddByte(h, separatorByte)
881 // Make sure label pairs are sorted. We depend on it for the consistency
882 // check.
883 if !sort.IsSorted(labelPairSorter(dtoMetric.Label)) {
884 // We cannot sort dtoMetric.Label in place as it is immutable by contract.
885 copiedLabels := make([]*dto.LabelPair, len(dtoMetric.Label))
886 copy(copiedLabels, dtoMetric.Label)
887 sort.Sort(labelPairSorter(copiedLabels))
888 dtoMetric.Label = copiedLabels
889 }
890 for _, lp := range dtoMetric.Label {
891 h = hashAdd(h, lp.GetName())
892 h = hashAddByte(h, separatorByte)
893 h = hashAdd(h, lp.GetValue())
894 h = hashAddByte(h, separatorByte)
895 }
896 if _, exists := metricHashes[h]; exists {
897 return fmt.Errorf(
898 "collected metric %q { %s} was collected before with the same name and label values",
899 name, dtoMetric,
900 )
901 }
902 metricHashes[h] = struct{}{}
903 return nil
904}
905
906func checkDescConsistency(
907 metricFamily *dto.MetricFamily,
908 dtoMetric *dto.Metric,
909 desc *Desc,
910) error {
911 // Desc help consistency with metric family help.
912 if metricFamily.GetHelp() != desc.help {
913 return fmt.Errorf(
914 "collected metric %s %s has help %q but should have %q",
915 metricFamily.GetName(), dtoMetric, metricFamily.GetHelp(), desc.help,
916 )
917 }
918
919 // Is the desc consistent with the content of the metric?
920 lpsFromDesc := make([]*dto.LabelPair, len(desc.constLabelPairs), len(dtoMetric.Label))
921 copy(lpsFromDesc, desc.constLabelPairs)
922 for _, l := range desc.variableLabels {
923 lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
924 Name: proto.String(l),
925 })
926 }
927 if len(lpsFromDesc) != len(dtoMetric.Label) {
928 return fmt.Errorf(
929 "labels in collected metric %s %s are inconsistent with descriptor %s",
930 metricFamily.GetName(), dtoMetric, desc,
931 )
932 }
933 sort.Sort(labelPairSorter(lpsFromDesc))
934 for i, lpFromDesc := range lpsFromDesc {
935 lpFromMetric := dtoMetric.Label[i]
936 if lpFromDesc.GetName() != lpFromMetric.GetName() ||
937 lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
938 return fmt.Errorf(
939 "labels in collected metric %s %s are inconsistent with descriptor %s",
940 metricFamily.GetName(), dtoMetric, desc,
941 )
942 }
943 }
944 return nil
945}