master
go 85 lines 2.63 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package prometheus
4
5 import (
6 "context"
7 "fmt"
8 "strings"
9
10 "github.com/netdata/netdata/go/plugins/pkg/prometheus"
11 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/prometheus/relabel"
12 )
13
14 // collect scrapes the endpoint and writes the metric families to the metrix store. The
15 // store cycle (begin/commit) is driven by the framework around Collect, so this only
16 // writes observations and returns an error to abort the cycle.
17 func (c *Collector) collect(ctx context.Context) error {
18 mfs, err := c.scrape(ctx)
19 if err != nil {
20 return err
21 }
22 c.writer.writeMetricFamilies(mfs)
23 return nil
24 }
25
26 // check probes the endpoint and enforces the startup gates the V1 collector applied once:
27 // the expected-prefix guard and the total time-series limit. Unlike V1 these are read-only
28 // (V1 mutated Config to make them one-shot); they run only at Check, i.e. autodetection.
29 func (c *Collector) check(ctx context.Context) error {
30 mfs, err := c.scrape(ctx)
31 if err != nil {
32 return err
33 }
34 if c.ExpectedPrefix != "" && !hasPrefix(mfs, c.ExpectedPrefix) {
35 return fmt.Errorf("'%s' metrics have no expected prefix (%s)", c.URL, c.ExpectedPrefix)
36 }
37 if c.MaxTS > 0 {
38 if n := calcMetrics(mfs); n > c.MaxTS {
39 return fmt.Errorf("'%s' num of time series (%d) > limit (%d)", c.URL, n, c.MaxTS)
40 }
41 }
42 if c.writer.countWritable(mfs) == 0 {
43 return fmt.Errorf("endpoint '%s' exposes no usable metrics", c.URL)
44 }
45 return nil
46 }
47
48 // scrape fetches the endpoint and enforces the empty-scrape contract: an empty scrape is
49 // an error (endpoint down or exposing nothing), not silent no-data.
50 func (c *Collector) scrape(ctx context.Context) (prometheus.MetricFamilies, error) {
51 mfs, err := c.prom.ScrapeWithTransform(ctx, c.relabelTransform)
52 if err != nil {
53 return nil, err
54 }
55 if mfs.Len() == 0 {
56 return nil, fmt.Errorf("endpoint '%s' returned 0 metric families", c.URL)
57 }
58 return mfs, nil
59 }
60
61 func hasPrefix(mfs prometheus.MetricFamilies, prefix string) bool {
62 for name := range mfs {
63 if strings.HasPrefix(name, prefix) {
64 return true
65 }
66 }
67 return false
68 }
69
70 func calcMetrics(mfs prometheus.MetricFamilies) int {
71 var n int
72 for _, mf := range mfs {
73 n += len(mf.Metrics())
74 }
75 return n
76 }
77
78 // onRelabelDrop logs why a relabel rule dropped a sample, at debug level. It logs
79 // the metric name and the rule outcome, never label values (cardinality/PII).
80 func (c *Collector) onRelabelDrop(s prometheus.Sample, d relabel.DropInfo) {
81 c.When(d.RuleIndex >= 0).
82 Debugf("relabel dropped metric %q: %s (rule %d, action %q)", s.Name, d.Reason, d.RuleIndex, d.Action).
83 Else().
84 Debugf("relabel dropped metric %q: %s", s.Name, d.Reason)
85 }