src/go/plugin/go.d/pkg/prometheus/client.go
// SPDX-License-Identifier: GPL-3.0-or-later
package prometheus
import (
"bufio"
"bytes"
"compress/gzip"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/prometheus/selector"
"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
)
type (
// Prometheus is a helper for scrape and parse prometheus format metrics.
Prometheus interface {
// ScrapeSeries and parse prometheus format metrics
ScrapeSeries() (Series, error)
Scrape() (MetricFamilies, error)
HTTPClient() *http.Client
}
prometheus struct {
client *http.Client
request web.RequestConfig
filepath string
sr selector.Selector
parser promTextParser
buf *bytes.Buffer
gzipr *gzip.Reader
bodyBuf *bufio.Reader
}
)
const (
acceptHeader = `text/plain;version=0.0.4;q=1,*/*;q=0.1`
)
// New creates a Prometheus instance.
func New(client *http.Client, request web.RequestConfig) Prometheus {
return &prometheus{
client: client,
request: request,
buf: bytes.NewBuffer(make([]byte, 0, 16000)),
}
}
// NewWithSelector creates a Prometheus instance with the selector.
func NewWithSelector(client *http.Client, request web.RequestConfig, sr selector.Selector) Prometheus {
p := &prometheus{
client: client,
request: request,
sr: sr,
buf: bytes.NewBuffer(make([]byte, 0, 16000)),
parser: promTextParser{sr: sr},
}
if v, err := url.Parse(request.URL); err == nil && v.Scheme == "file" {
p.filepath = filepath.Join(v.Host, v.Path)
}
return p
}
func (p *prometheus) HTTPClient() *http.Client {
return p.client
}
// ScrapeSeries scrapes metrics, parses and sorts
func (p *prometheus) ScrapeSeries() (Series, error) {
p.buf.Reset()
if err := p.fetch(p.buf); err != nil {
return nil, err
}
return p.parser.parseToSeries(p.buf.Bytes())
}
func (p *prometheus) Scrape() (MetricFamilies, error) {
p.buf.Reset()
if err := p.fetch(p.buf); err != nil {
return nil, err
}
return p.parser.parseToMetricFamilies(p.buf.Bytes())
}
func (p *prometheus) fetch(w io.Writer) error {
// TODO: should be a separate text file prom client
if p.filepath != "" {
f, err := os.Open(p.filepath)
if err != nil {
return err
}
defer func() { _ = f.Close() }()
_, err = io.Copy(w, f)
return err
}
req, err := web.NewHTTPRequest(p.request)
if err != nil {
return err
}
req.Header.Add("Accept", acceptHeader)
req.Header.Add("Accept-Encoding", "gzip")
resp, err := p.client.Do(req)
if err != nil {
return err
}
defer web.CloseBody(resp)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("server '%s' returned HTTP status code %d (%s)", req.URL, resp.StatusCode, resp.Status)
}
if resp.Header.Get("Content-Encoding") != "gzip" {
_, err = io.Copy(w, resp.Body)
return err
}
if p.gzipr == nil {
p.bodyBuf = bufio.NewReader(resp.Body)
p.gzipr, err = gzip.NewReader(p.bodyBuf)
if err != nil {
return err
}
} else {
p.bodyBuf.Reset(resp.Body)
_ = p.gzipr.Reset(p.bodyBuf)
}
_, err = io.Copy(w, p.gzipr)
_ = p.gzipr.Close()
return err
}