| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package prometheus |
| 4 | |
| 5 | import ( |
| 6 | "bytes" |
| 7 | "context" |
| 8 | "net/http" |
| 9 | "net/url" |
| 10 | "path/filepath" |
| 11 | |
| 12 | "github.com/netdata/netdata/go/plugins/pkg/prometheus/selector" |
| 13 | "github.com/netdata/netdata/go/plugins/pkg/web" |
| 14 | ) |
| 15 | |
| 16 | type ( |
| 17 | // Prometheus is a helper for scrape and parse prometheus format metrics. |
| 18 | Prometheus interface { |
| 19 | // ScrapeSeries and parse prometheus format metrics |
| 20 | ScrapeSeries() (Series, error) |
| 21 | Scrape() (MetricFamilies, error) |
| 22 | // ScrapeWithTransform scrapes, runs transform on every sample (a flat |
| 23 | // [Sample] stream before typed-family assembly, with the selector already |
| 24 | // applied), then assembles the kept, transformed samples into MetricFamilies. |
| 25 | // A nil transform behaves exactly like Scrape. The result aliases reused |
| 26 | // buffers, valid until the next scrape on this instance (same as Scrape). |
| 27 | // |
| 28 | // Samples reach transform in exposition order, with one exception: a |
| 29 | // _sum/_count sample whose family type is not yet known (its # TYPE, first |
| 30 | // bucket, or first quantile has not appeared) is deferred and delivered once |
| 31 | // the type resolves, or at end of scrape — so it may arrive after a later, |
| 32 | // unrelated sample. A per-sample (stateless) transform is unaffected. |
| 33 | ScrapeWithTransform(ctx context.Context, transform SampleTransform) (MetricFamilies, error) |
| 34 | HTTPClient() *http.Client |
| 35 | } |
| 36 | |
| 37 | prometheus struct { |
| 38 | client *http.Client |
| 39 | src fetcher |
| 40 | |
| 41 | parser promTextParser |
| 42 | |
| 43 | buf *bytes.Buffer |
| 44 | } |
| 45 | ) |
| 46 | |
| 47 | // New creates a Prometheus instance. |
| 48 | func New(client *http.Client, request web.RequestConfig) Prometheus { |
| 49 | return NewWithSelector(client, request, nil) |
| 50 | } |
| 51 | |
| 52 | // NewWithSelector creates a Prometheus instance with the selector. |
| 53 | func NewWithSelector(client *http.Client, request web.RequestConfig, sr selector.Selector) Prometheus { |
| 54 | p := &prometheus{ |
| 55 | client: client, |
| 56 | buf: bytes.NewBuffer(make([]byte, 0, 16000)), |
| 57 | parser: promTextParser{sr: sr}, |
| 58 | } |
| 59 | |
| 60 | if v, err := url.Parse(request.URL); err == nil && v.Scheme == "file" { |
| 61 | p.src = &fileFetcher{path: filepath.Join(v.Host, v.Path)} |
| 62 | } else { |
| 63 | p.src = &httpFetcher{client: client, request: request} |
| 64 | } |
| 65 | |
| 66 | return p |
| 67 | } |
| 68 | |
| 69 | func (p *prometheus) HTTPClient() *http.Client { |
| 70 | return p.client |
| 71 | } |
| 72 | |
| 73 | // ScrapeSeries scrapes metrics, parses and sorts |
| 74 | func (p *prometheus) ScrapeSeries() (Series, error) { |
| 75 | p.buf.Reset() |
| 76 | |
| 77 | if err := p.src.fetch(context.Background(), p.buf); err != nil { |
| 78 | return nil, err |
| 79 | } |
| 80 | |
| 81 | return p.parser.parseToSeries(p.buf.Bytes()) |
| 82 | } |
| 83 | |
| 84 | func (p *prometheus) Scrape() (MetricFamilies, error) { |
| 85 | return p.ScrapeWithTransform(context.Background(), nil) |
| 86 | } |
| 87 | |
| 88 | func (p *prometheus) ScrapeWithTransform(ctx context.Context, transform SampleTransform) (MetricFamilies, error) { |
| 89 | p.buf.Reset() |
| 90 | |
| 91 | if err := p.src.fetch(ctx, p.buf); err != nil { |
| 92 | return nil, err |
| 93 | } |
| 94 | |
| 95 | return p.parser.parseToMetricFamilies(p.buf.Bytes(), transform) |
| 96 | } |