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