summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/pkg/prometheus
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/client.go155
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/client_test.go137
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/metric_family.go116
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/metric_family_test.go356
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/metric_series.go110
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/metric_series_test.go140
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/parse.go414
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/parse_test.go1675
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/selector/README.md102
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/selector/expr.go62
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/selector/expr_test.go231
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/selector/logical.go49
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/selector/logical_test.go226
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/selector/parse.go97
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/selector/parse_test.go117
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/selector/selector.go52
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/selector/selector_test.go11
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/counter-meta.txt11
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/counter-no-meta.txt8
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/gauge-meta.txt11
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/gauge-no-meta.txt8
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/histogram-meta.txt43
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/histogram-no-meta.txt40
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/multiline-help.txt3
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/summary-meta.txt43
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/summary-no-meta.txt40
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/testdata.nometa.txt410
-rw-r--r--src/go/collectors/go.d.plugin/pkg/prometheus/testdata/testdata.txt528
28 files changed, 5195 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/client.go b/src/go/collectors/go.d.plugin/pkg/prometheus/client.go
new file mode 100644
index 000000000..3365b270c
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/client.go
@@ -0,0 +1,155 @@
+// 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/go.d.plugin/pkg/prometheus/selector"
+ "github.com/netdata/netdata/go/go.d.plugin/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.Request
+ 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.Request) 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.Request, 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 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 func() {
+ _, _ = io.Copy(io.Discard, resp.Body)
+ _ = resp.Body.Close()
+ }()
+
+ 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
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/client_test.go b/src/go/collectors/go.d.plugin/pkg/prometheus/client_test.go
new file mode 100644
index 000000000..76199800a
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/client_test.go
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package prometheus
+
+import (
+ "bytes"
+ "compress/gzip"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/prometheus/selector"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ testData, _ = os.ReadFile("testdata/testdata.txt")
+ testDataNoMeta, _ = os.ReadFile("testdata/testdata.nometa.txt")
+)
+
+func Test_testClientDataIsValid(t *testing.T) {
+ for name, data := range map[string][]byte{
+ "testData": testData,
+ } {
+ require.NotNilf(t, data, name)
+ }
+}
+
+func TestPrometheus404(t *testing.T) {
+ tsMux := http.NewServeMux()
+ tsMux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(404)
+ })
+ ts := httptest.NewServer(tsMux)
+ defer ts.Close()
+
+ req := web.Request{URL: ts.URL + "/metrics"}
+ prom := New(http.DefaultClient, req)
+ res, err := prom.ScrapeSeries()
+
+ assert.Error(t, err)
+ assert.Nil(t, res)
+}
+
+func TestPrometheusPlain(t *testing.T) {
+ tsMux := http.NewServeMux()
+ tsMux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
+ _, _ = w.Write(testData)
+ })
+ ts := httptest.NewServer(tsMux)
+ defer ts.Close()
+
+ req := web.Request{URL: ts.URL + "/metrics"}
+ prom := New(http.DefaultClient, req)
+ res, err := prom.ScrapeSeries()
+
+ assert.NoError(t, err)
+ verifyTestData(t, res)
+}
+
+func TestPrometheusPlainWithSelector(t *testing.T) {
+ tsMux := http.NewServeMux()
+ tsMux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
+ _, _ = w.Write(testData)
+ })
+ ts := httptest.NewServer(tsMux)
+ defer ts.Close()
+
+ req := web.Request{URL: ts.URL + "/metrics"}
+ sr, err := selector.Parse("go_gc*")
+ require.NoError(t, err)
+ prom := NewWithSelector(http.DefaultClient, req, sr)
+
+ res, err := prom.ScrapeSeries()
+ require.NoError(t, err)
+
+ for _, v := range res {
+ assert.Truef(t, strings.HasPrefix(v.Name(), "go_gc"), v.Name())
+ }
+}
+
+func TestPrometheusGzip(t *testing.T) {
+ counter := 0
+ rawTestData := [][]byte{testData, testDataNoMeta}
+ tsMux := http.NewServeMux()
+ tsMux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Encoding", "gzip")
+ w.WriteHeader(200)
+ gz := new(bytes.Buffer)
+ ww := gzip.NewWriter(gz)
+ _, _ = ww.Write(rawTestData[counter])
+ _ = ww.Close()
+ _, _ = gz.WriteTo(w)
+ counter++
+ })
+ ts := httptest.NewServer(tsMux)
+ defer ts.Close()
+
+ req := web.Request{URL: ts.URL + "/metrics"}
+ prom := New(http.DefaultClient, req)
+
+ for i := 0; i < 2; i++ {
+ res, err := prom.ScrapeSeries()
+ assert.NoError(t, err)
+ verifyTestData(t, res)
+ }
+}
+
+func TestPrometheusReadFromFile(t *testing.T) {
+ req := web.Request{URL: "file://testdata/testdata.txt"}
+ prom := NewWithSelector(http.DefaultClient, req, nil)
+
+ for i := 0; i < 2; i++ {
+ res, err := prom.ScrapeSeries()
+ assert.NoError(t, err)
+ verifyTestData(t, res)
+ }
+}
+
+func verifyTestData(t *testing.T, ms Series) {
+ assert.Equal(t, 410, len(ms))
+ assert.Equal(t, "go_gc_duration_seconds", ms[0].Labels.Get("__name__"))
+ assert.Equal(t, "0.25", ms[0].Labels.Get("quantile"))
+ assert.InDelta(t, 4.9351e-05, ms[0].Value, 0.0001)
+
+ notExistYet := ms.FindByName("not_exist_yet")
+ assert.NotNil(t, notExistYet)
+ assert.Len(t, notExistYet, 0)
+
+ targetInterval := ms.FindByName("prometheus_target_interval_length_seconds")
+ assert.Len(t, targetInterval, 5)
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/metric_family.go b/src/go/collectors/go.d.plugin/pkg/prometheus/metric_family.go
new file mode 100644
index 000000000..dde08801e
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/metric_family.go
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package prometheus
+
+import (
+ "github.com/prometheus/common/model"
+ "github.com/prometheus/prometheus/model/labels"
+)
+
+type (
+ MetricFamilies map[string]*MetricFamily
+
+ MetricFamily struct {
+ name string
+ help string
+ typ model.MetricType
+ metrics []Metric
+ }
+ Metric struct {
+ labels []labels.Label
+ gauge *Gauge
+ counter *Counter
+ summary *Summary
+ histogram *Histogram
+ untyped *Untyped
+ }
+ Gauge struct {
+ value float64
+ }
+ Counter struct {
+ value float64
+ }
+ Summary struct {
+ sum float64
+ count float64
+ quantiles []Quantile
+ }
+ Quantile struct {
+ quantile float64
+ value float64
+ }
+ Histogram struct {
+ sum float64
+ count float64
+ buckets []Bucket
+ }
+ Bucket struct {
+ upperBound float64
+ cumulativeCount float64
+ }
+ Untyped struct {
+ value float64
+ }
+)
+
+func (mfs MetricFamilies) Len() int {
+ return len(mfs)
+}
+
+func (mfs MetricFamilies) Get(name string) *MetricFamily {
+ return (mfs)[name]
+}
+
+func (mfs MetricFamilies) GetGauge(name string) *MetricFamily {
+ return mfs.get(name, model.MetricTypeGauge)
+}
+
+func (mfs MetricFamilies) GetCounter(name string) *MetricFamily {
+ return mfs.get(name, model.MetricTypeCounter)
+}
+
+func (mfs MetricFamilies) GetSummary(name string) *MetricFamily {
+ return mfs.get(name, model.MetricTypeSummary)
+}
+
+func (mfs MetricFamilies) GetHistogram(name string) *MetricFamily {
+ return mfs.get(name, model.MetricTypeHistogram)
+}
+
+func (mfs MetricFamilies) get(name string, typ model.MetricType) *MetricFamily {
+ mf := mfs.Get(name)
+ if mf == nil || mf.typ != typ {
+ return nil
+ }
+ return mf
+}
+
+func (mf *MetricFamily) Name() string { return mf.name }
+func (mf *MetricFamily) Help() string { return mf.help }
+func (mf *MetricFamily) Type() model.MetricType { return mf.typ }
+func (mf *MetricFamily) Metrics() []Metric { return mf.metrics }
+
+func (m *Metric) Labels() labels.Labels { return m.labels }
+func (m *Metric) Gauge() *Gauge { return m.gauge }
+func (m *Metric) Counter() *Counter { return m.counter }
+func (m *Metric) Summary() *Summary { return m.summary }
+func (m *Metric) Histogram() *Histogram { return m.histogram }
+func (m *Metric) Untyped() *Untyped { return m.untyped }
+
+func (g Gauge) Value() float64 { return g.value }
+func (c Counter) Value() float64 { return c.value }
+func (u Untyped) Value() float64 { return u.value }
+
+func (s Summary) Count() float64 { return s.count }
+func (s Summary) Sum() float64 { return s.sum }
+func (s Summary) Quantiles() []Quantile { return s.quantiles }
+
+func (q Quantile) Quantile() float64 { return q.quantile }
+func (q Quantile) Value() float64 { return q.value }
+
+func (h Histogram) Count() float64 { return h.count }
+func (h Histogram) Sum() float64 { return h.sum }
+func (h Histogram) Buckets() []Bucket { return h.buckets }
+
+func (b Bucket) UpperBound() float64 { return b.upperBound }
+func (b Bucket) CumulativeCount() float64 { return b.cumulativeCount }
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/metric_family_test.go b/src/go/collectors/go.d.plugin/pkg/prometheus/metric_family_test.go
new file mode 100644
index 000000000..f373996da
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/metric_family_test.go
@@ -0,0 +1,356 @@
+package prometheus
+
+import (
+ "testing"
+
+ "github.com/prometheus/common/model"
+ "github.com/prometheus/prometheus/model/labels"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMetricFamilies_Len(t *testing.T) {
+ tests := map[string]struct {
+ mfs MetricFamilies
+ wantLen int
+ }{
+ "initialized with two elements": {
+ mfs: MetricFamilies{"1": nil, "2": nil},
+ wantLen: 2,
+ },
+ "not initialized": {
+ mfs: nil,
+ wantLen: 0,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.mfs.Len(), test.wantLen)
+ })
+ }
+}
+
+func TestMetricFamilies_Get(t *testing.T) {
+ const n = "metric"
+
+ tests := map[string]struct {
+ mfs MetricFamilies
+ wantMF *MetricFamily
+ }{
+ "etric is found": {
+ mfs: MetricFamilies{n: &MetricFamily{name: n}},
+ wantMF: &MetricFamily{name: n},
+ },
+ "metric is not found": {
+ mfs: MetricFamilies{"!" + n: &MetricFamily{name: n}},
+ wantMF: nil,
+ },
+ "not initialized": {
+ mfs: nil,
+ wantMF: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.mfs.Get(n), test.wantMF)
+ })
+ }
+}
+
+func TestMetricFamilies_GetGauge(t *testing.T) {
+ const n = "metric"
+
+ tests := map[string]struct {
+ mfs MetricFamilies
+ wantMF *MetricFamily
+ }{
+ "metric is found and is Gauge": {
+ mfs: MetricFamilies{n: &MetricFamily{name: n, typ: model.MetricTypeGauge}},
+ wantMF: &MetricFamily{name: n, typ: model.MetricTypeGauge},
+ },
+ "metric is found but it is not Gauge": {
+ mfs: MetricFamilies{n: &MetricFamily{name: n, typ: model.MetricTypeUnknown}},
+ wantMF: nil,
+ },
+ "metric is not found": {
+ mfs: MetricFamilies{"!" + n: &MetricFamily{name: n, typ: model.MetricTypeGauge}},
+ wantMF: nil,
+ },
+ "not initialized": {
+ mfs: nil,
+ wantMF: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.mfs.GetGauge(n), test.wantMF)
+ })
+ }
+}
+
+func TestMetricFamilies_GetCounter(t *testing.T) {
+ const n = "metric"
+
+ tests := map[string]struct {
+ mfs MetricFamilies
+ wantMF *MetricFamily
+ }{
+ "metric is found and is Counter": {
+ mfs: MetricFamilies{n: &MetricFamily{name: n, typ: model.MetricTypeCounter}},
+ wantMF: &MetricFamily{name: n, typ: model.MetricTypeCounter},
+ },
+ "metric is found but it is not Counter": {
+ mfs: MetricFamilies{n: &MetricFamily{name: n, typ: model.MetricTypeGauge}},
+ wantMF: nil,
+ },
+ "metric is not found": {
+ mfs: MetricFamilies{"!" + n: &MetricFamily{name: n, typ: model.MetricTypeGauge}},
+ wantMF: nil,
+ },
+ "not initialized": {
+ mfs: nil,
+ wantMF: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.mfs.GetCounter(n), test.wantMF)
+ })
+ }
+}
+
+func TestMetricFamilies_GetSummary(t *testing.T) {
+ const n = "metric"
+
+ tests := map[string]struct {
+ mfs MetricFamilies
+ wantMF *MetricFamily
+ }{
+ "metric is found and is Summary": {
+ mfs: MetricFamilies{n: &MetricFamily{name: n, typ: model.MetricTypeSummary}},
+ wantMF: &MetricFamily{name: n, typ: model.MetricTypeSummary},
+ },
+ "metric is found but it is not Summary": {
+ mfs: MetricFamilies{n: &MetricFamily{name: n, typ: model.MetricTypeGauge}},
+ wantMF: nil,
+ },
+ "metric is not found": {
+ mfs: MetricFamilies{"!" + n: &MetricFamily{name: n, typ: model.MetricTypeGauge}},
+ wantMF: nil,
+ },
+ "not initialized": {
+ mfs: nil,
+ wantMF: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.mfs.GetSummary(n), test.wantMF)
+ })
+ }
+}
+
+func TestMetricFamilies_GetHistogram(t *testing.T) {
+ const n = "metric"
+
+ tests := map[string]struct {
+ mfs MetricFamilies
+ wantMF *MetricFamily
+ }{
+ "metric is found and is Histogram": {
+ mfs: MetricFamilies{n: &MetricFamily{name: n, typ: model.MetricTypeHistogram}},
+ wantMF: &MetricFamily{name: n, typ: model.MetricTypeHistogram},
+ },
+ "metric is found but it is not Histogram": {
+ mfs: MetricFamilies{n: &MetricFamily{name: n, typ: model.MetricTypeGauge}},
+ wantMF: nil,
+ },
+ "metric is not found": {
+ mfs: MetricFamilies{"!" + n: &MetricFamily{name: n, typ: model.MetricTypeGauge}},
+ wantMF: nil,
+ },
+ "not initialized": {
+ mfs: nil,
+ wantMF: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.mfs.GetHistogram(n), test.wantMF)
+ })
+ }
+}
+
+func TestMetricFamily_Name(t *testing.T) {
+ mf := &MetricFamily{name: "name"}
+ assert.Equal(t, mf.Name(), "name")
+}
+
+func TestMetricFamily_Type(t *testing.T) {
+ mf := &MetricFamily{typ: model.MetricTypeGauge}
+ assert.Equal(t, mf.Type(), model.MetricTypeGauge)
+}
+
+func TestMetricFamily_Help(t *testing.T) {
+ mf := &MetricFamily{help: "help"}
+ assert.Equal(t, mf.Help(), "help")
+}
+
+func TestMetricFamily_Metrics(t *testing.T) {
+ metrics := []Metric{{gauge: &Gauge{value: 1}, counter: &Counter{value: 1}}}
+ mf := &MetricFamily{metrics: metrics}
+ assert.Equal(t, mf.Metrics(), metrics)
+}
+
+func TestMetric_Labels(t *testing.T) {
+ lbs := labels.Labels{{Name: "1", Value: "1"}, {Name: "2", Value: "2"}}
+ m := &Metric{labels: lbs}
+ assert.Equal(t, m.Labels(), lbs)
+}
+
+func TestMetric_Gauge(t *testing.T) {
+ tests := map[string]struct {
+ m *Metric
+ want *Gauge
+ }{
+ "gauge set": {
+ m: &Metric{gauge: &Gauge{value: 1}},
+ want: &Gauge{value: 1},
+ },
+ "gauge not set": {
+ m: &Metric{},
+ want: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.m.Gauge(), test.want)
+ })
+ }
+}
+
+func TestMetric_Counter(t *testing.T) {
+ tests := map[string]struct {
+ m *Metric
+ want *Counter
+ }{
+ "counter set": {
+ m: &Metric{counter: &Counter{value: 1}},
+ want: &Counter{value: 1},
+ },
+ "counter not set": {
+ m: &Metric{},
+ want: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.m.Counter(), test.want)
+ })
+ }
+}
+
+func TestMetric_Summary(t *testing.T) {
+ tests := map[string]struct {
+ m *Metric
+ want *Summary
+ }{
+ "summary set": {
+ m: &Metric{summary: &Summary{sum: 0.1, count: 3}},
+ want: &Summary{sum: 0.1, count: 3},
+ },
+ "summary not set": {
+ m: &Metric{},
+ want: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.m.Summary(), test.want)
+ })
+ }
+}
+
+func TestMetric_Histogram(t *testing.T) {
+ tests := map[string]struct {
+ m *Metric
+ want *Histogram
+ }{
+ "histogram set": {
+ m: &Metric{histogram: &Histogram{sum: 0.1, count: 3}},
+ want: &Histogram{sum: 0.1, count: 3},
+ },
+ "histogram not set": {
+ m: &Metric{},
+ want: nil,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.m.Histogram(), test.want)
+ })
+ }
+}
+
+func TestGauge_Value(t *testing.T) {
+ assert.Equal(t, Gauge{value: 1}.Value(), 1.0)
+}
+
+func TestCounter_Value(t *testing.T) {
+ assert.Equal(t, Counter{value: 1}.Value(), 1.0)
+}
+
+func TestSummary_Sum(t *testing.T) {
+ assert.Equal(t, Summary{sum: 1}.Sum(), 1.0)
+}
+
+func TestSummary_Count(t *testing.T) {
+ assert.Equal(t, Summary{count: 1}.Count(), 1.0)
+}
+
+func TestSummary_Quantiles(t *testing.T) {
+ assert.Equal(t,
+ Summary{quantiles: []Quantile{{quantile: 0.1, value: 1}}}.Quantiles(),
+ []Quantile{{quantile: 0.1, value: 1}},
+ )
+}
+
+func TestQuantile_Value(t *testing.T) {
+ assert.Equal(t, Quantile{value: 1}.Value(), 1.0)
+}
+
+func TestQuantile_Quantile(t *testing.T) {
+ assert.Equal(t, Quantile{quantile: 0.1}.Quantile(), 0.1)
+}
+
+func TestHistogram_Sum(t *testing.T) {
+ assert.Equal(t, Histogram{sum: 1}.Sum(), 1.0)
+}
+
+func TestHistogram_Count(t *testing.T) {
+ assert.Equal(t, Histogram{count: 1}.Count(), 1.0)
+}
+
+func TestHistogram_Buckets(t *testing.T) {
+ assert.Equal(t,
+ Histogram{buckets: []Bucket{{upperBound: 0.1, cumulativeCount: 1}}}.Buckets(),
+ []Bucket{{upperBound: 0.1, cumulativeCount: 1}},
+ )
+}
+
+func TestBucket_UpperBound(t *testing.T) {
+ assert.Equal(t, Bucket{upperBound: 0.1}.UpperBound(), 0.1)
+}
+
+func TestBucket_CumulativeCount(t *testing.T) {
+ assert.Equal(t, Bucket{cumulativeCount: 1}.CumulativeCount(), 1.0)
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/metric_series.go b/src/go/collectors/go.d.plugin/pkg/prometheus/metric_series.go
new file mode 100644
index 000000000..31914f4b2
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/metric_series.go
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package prometheus
+
+import (
+ "sort"
+
+ "github.com/prometheus/prometheus/model/labels"
+)
+
+type (
+ // SeriesSample is a pair of label set and value
+ SeriesSample struct {
+ Labels labels.Labels
+ Value float64
+ }
+
+ // Series is a list of SeriesSample
+ Series []SeriesSample
+)
+
+// Name the __name__ label value
+func (s SeriesSample) Name() string {
+ return s.Labels[0].Value
+}
+
+// Add appends a metric.
+func (s *Series) Add(kv SeriesSample) {
+ *s = append(*s, kv)
+}
+
+// Reset resets the buffer to be empty,
+// but it retains the underlying storage for use by future writes.
+func (s *Series) Reset() {
+ *s = (*s)[:0]
+}
+
+// Sort sorts data.
+func (s Series) Sort() {
+ sort.Sort(s)
+}
+
+// Len returns metric length.
+func (s Series) Len() int {
+ return len(s)
+}
+
+// Less reports whether the element with
+// index i should sort before the element with index j.
+func (s Series) Less(i, j int) bool {
+ return s[i].Name() < s[j].Name()
+}
+
+// Swap swaps the elements with indexes i and j.
+func (s Series) Swap(i, j int) {
+ s[i], s[j] = s[j], s[i]
+}
+
+// FindByName finds metrics where it's __name__ label matches given name.
+// It expects the metrics is sorted.
+// Complexity: O(log(N))
+func (s Series) FindByName(name string) Series {
+ from := sort.Search(len(s), func(i int) bool {
+ return s[i].Name() >= name
+ })
+ if from == len(s) || s[from].Name() != name { // not found
+ return Series{}
+ }
+ until := from + 1
+ for until < len(s) && s[until].Name() == name {
+ until++
+ }
+ return s[from:until]
+}
+
+// FindByNames finds metrics where it's __name__ label matches given any of names.
+// It expects the metrics is sorted.
+// Complexity: O(log(N))
+func (s Series) FindByNames(names ...string) Series {
+ switch len(names) {
+ case 0:
+ return Series{}
+ case 1:
+ return s.FindByName(names[0])
+ }
+ var result Series
+ for _, name := range names {
+ result = append(result, s.FindByName(name)...)
+ }
+ return result
+}
+
+// Max returns the max value.
+// It does NOT expect the metrics is sorted.
+// Complexity: O(N)
+func (s Series) Max() float64 {
+ switch len(s) {
+ case 0:
+ return 0
+ case 1:
+ return s[0].Value
+ }
+ max := s[0].Value
+ for _, kv := range s[1:] {
+ if max < kv.Value {
+ max = kv.Value
+ }
+ }
+ return max
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/metric_series_test.go b/src/go/collectors/go.d.plugin/pkg/prometheus/metric_series_test.go
new file mode 100644
index 000000000..80c805474
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/metric_series_test.go
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package prometheus
+
+import (
+ "testing"
+
+ "github.com/prometheus/prometheus/model/labels"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TODO: write better tests
+
+const (
+ testName1 = "logback_events_total"
+ testName2 = "jvm_threads_peak"
+)
+
+func newTestSeries() Series {
+ return Series{
+ {
+ Value: 10,
+ Labels: labels.Labels{
+ {Name: "__name__", Value: testName1},
+ {Name: "level", Value: "error"},
+ },
+ },
+ {
+ Value: 20,
+ Labels: labels.Labels{
+ {Name: "__name__", Value: testName1},
+ {Name: "level", Value: "warn"},
+ },
+ },
+ {
+ Value: 5,
+ Labels: labels.Labels{
+ {Name: "__name__", Value: testName1},
+ {Name: "level", Value: "info"},
+ },
+ },
+ {
+ Value: 15,
+ Labels: labels.Labels{
+ {Name: "__name__", Value: testName1},
+ {Name: "level", Value: "debug"},
+ },
+ },
+ {
+ Value: 26,
+ Labels: labels.Labels{
+ {Name: "__name__", Value: testName2},
+ },
+ },
+ }
+}
+
+func TestSeries_Name(t *testing.T) {
+ m := newTestSeries()
+
+ assert.Equal(t, testName1, m[0].Name())
+ assert.Equal(t, testName1, m[1].Name())
+
+}
+
+func TestSeries_Add(t *testing.T) {
+ m := newTestSeries()
+
+ require.Len(t, m, 5)
+ m.Add(SeriesSample{})
+ assert.Len(t, m, 6)
+}
+
+func TestSeries_FindByName(t *testing.T) {
+ m := newTestSeries()
+ m.Sort()
+ assert.Len(t, Series{}.FindByName(testName1), 0)
+ assert.Len(t, m.FindByName(testName1), len(m)-1)
+}
+
+func TestSeries_FindByNames(t *testing.T) {
+ m := newTestSeries()
+ m.Sort()
+ assert.Len(t, m.FindByNames(), 0)
+ assert.Len(t, m.FindByNames(testName1), len(m)-1)
+ assert.Len(t, m.FindByNames(testName1, testName2), len(m))
+}
+
+func TestSeries_Len(t *testing.T) {
+ m := newTestSeries()
+
+ assert.Equal(t, len(m), m.Len())
+}
+
+func TestSeries_Less(t *testing.T) {
+ m := newTestSeries()
+
+ assert.False(t, m.Less(0, 1))
+ assert.True(t, m.Less(4, 0))
+}
+
+func TestSeries_Max(t *testing.T) {
+ m := newTestSeries()
+
+ assert.Equal(t, float64(26), m.Max())
+
+}
+
+func TestSeries_Reset(t *testing.T) {
+ m := newTestSeries()
+ m.Reset()
+
+ assert.Len(t, m, 0)
+
+}
+
+func TestSeries_Sort(t *testing.T) {
+ {
+ m := newTestSeries()
+ m.Sort()
+ assert.Equal(t, testName2, m[0].Name())
+ }
+ {
+ m := Series{}
+ assert.Equal(t, 0.0, m.Max())
+ }
+}
+
+func TestSeries_Swap(t *testing.T) {
+ m := newTestSeries()
+
+ m0 := m[0]
+ m1 := m[1]
+
+ m.Swap(0, 1)
+
+ assert.Equal(t, m0, m[1])
+ assert.Equal(t, m1, m[0])
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/parse.go b/src/go/collectors/go.d.plugin/pkg/prometheus/parse.go
new file mode 100644
index 000000000..958d66289
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/parse.go
@@ -0,0 +1,414 @@
+package prometheus
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/prometheus/selector"
+
+ "github.com/prometheus/common/model"
+ "github.com/prometheus/prometheus/model/labels"
+ "github.com/prometheus/prometheus/model/textparse"
+)
+
+const (
+ quantileLabel = "quantile"
+ bucketLabel = "le"
+)
+
+const (
+ countSuffix = "_count"
+ sumSuffix = "_sum"
+ bucketSuffix = "_bucket"
+)
+
+type promTextParser struct {
+ metrics MetricFamilies
+ series Series
+
+ sr selector.Selector
+
+ currMF *MetricFamily
+ currSeries labels.Labels
+
+ summaries map[uint64]*Summary
+ histograms map[uint64]*Histogram
+
+ isCount bool
+ isSum bool
+ isQuantile bool
+ isBucket bool
+
+ currQuantile float64
+ currBucket float64
+}
+
+func (p *promTextParser) parseToSeries(text []byte) (Series, error) {
+ p.series.Reset()
+
+ parser := textparse.NewPromParser(text)
+ for {
+ entry, err := parser.Next()
+ if err != nil {
+ if errors.Is(err, io.EOF) {
+ break
+ }
+ if entry == textparse.EntryInvalid && strings.HasPrefix(err.Error(), "invalid metric type") {
+ continue
+ }
+ return nil, fmt.Errorf("failed to parse prometheus metrics: %v", err)
+ }
+
+ switch entry {
+ case textparse.EntrySeries:
+ p.currSeries = p.currSeries[:0]
+
+ parser.Metric(&p.currSeries)
+
+ if p.sr != nil && !p.sr.Matches(p.currSeries) {
+ continue
+ }
+
+ _, _, val := parser.Series()
+ p.series.Add(SeriesSample{Labels: copyLabels(p.currSeries), Value: val})
+ }
+ }
+
+ p.series.Sort()
+
+ return p.series, nil
+}
+
+var reSpace = regexp.MustCompile(`\s+`)
+
+func (p *promTextParser) parseToMetricFamilies(text []byte) (MetricFamilies, error) {
+ p.reset()
+
+ parser := textparse.NewPromParser(text)
+ for {
+ entry, err := parser.Next()
+ if err != nil {
+ if errors.Is(err, io.EOF) {
+ break
+ }
+ if entry == textparse.EntryInvalid && strings.HasPrefix(err.Error(), "invalid metric type") {
+ continue
+ }
+ return nil, fmt.Errorf("failed to parse prometheus metrics: %v", err)
+ }
+
+ switch entry {
+ case textparse.EntryHelp:
+ name, help := parser.Help()
+ p.setMetricFamilyByName(string(name))
+ p.currMF.help = string(help)
+ if strings.IndexByte(p.currMF.help, '\n') != -1 {
+ // convert multiline to one line because HELP is used as the chart title.
+ p.currMF.help = reSpace.ReplaceAllString(strings.TrimSpace(p.currMF.help), " ")
+ }
+ case textparse.EntryType:
+ name, typ := parser.Type()
+ p.setMetricFamilyByName(string(name))
+ p.currMF.typ = typ
+ case textparse.EntrySeries:
+ p.currSeries = p.currSeries[:0]
+
+ parser.Metric(&p.currSeries)
+
+ if p.sr != nil && !p.sr.Matches(p.currSeries) {
+ continue
+ }
+
+ p.setMetricFamilyBySeries()
+
+ _, _, value := parser.Series()
+
+ switch p.currMF.typ {
+ case model.MetricTypeGauge:
+ p.addGauge(value)
+ case model.MetricTypeCounter:
+ p.addCounter(value)
+ case model.MetricTypeSummary:
+ p.addSummary(value)
+ case model.MetricTypeHistogram:
+ p.addHistogram(value)
+ case model.MetricTypeUnknown:
+ p.addUnknown(value)
+ }
+ }
+ }
+
+ for k, v := range p.metrics {
+ if len(v.Metrics()) == 0 {
+ delete(p.metrics, k)
+ }
+ }
+
+ return p.metrics, nil
+}
+
+func (p *promTextParser) setMetricFamilyByName(name string) {
+ mf, ok := p.metrics[name]
+ if !ok {
+ mf = &MetricFamily{name: name, typ: model.MetricTypeUnknown}
+ p.metrics[name] = mf
+ }
+ p.currMF = mf
+}
+
+func (p *promTextParser) setMetricFamilyBySeries() {
+ p.isSum, p.isCount, p.isQuantile, p.isBucket = false, false, false, false
+ p.currQuantile, p.currBucket = 0, 0
+
+ name := p.currSeries[0].Value
+
+ if p.currMF != nil && p.currMF.name == name {
+ if p.currMF.typ == model.MetricTypeSummary {
+ p.setQuantile()
+ }
+ return
+ }
+
+ typ := model.MetricTypeUnknown
+
+ switch {
+ case strings.HasSuffix(name, sumSuffix):
+ n := strings.TrimSuffix(name, sumSuffix)
+ if mf, ok := p.metrics[n]; ok && isSummaryOrHistogram(mf.typ) {
+ p.isSum = true
+ p.currSeries[0].Value = n
+ p.currMF = mf
+ return
+ }
+ case strings.HasSuffix(name, countSuffix):
+ n := strings.TrimSuffix(name, countSuffix)
+ if mf, ok := p.metrics[n]; ok && isSummaryOrHistogram(mf.typ) {
+ p.isCount = true
+ p.currSeries[0].Value = n
+ p.currMF = mf
+ return
+ }
+ case strings.HasSuffix(name, bucketSuffix):
+ n := strings.TrimSuffix(name, bucketSuffix)
+ if mf, ok := p.metrics[n]; ok && isSummaryOrHistogram(mf.typ) {
+ p.currSeries[0].Value = n
+ p.setBucket()
+ p.currMF = mf
+ return
+ }
+ if p.currSeries.Has(bucketLabel) {
+ p.currSeries[0].Value = n
+ p.setBucket()
+ name = n
+ typ = model.MetricTypeHistogram
+ }
+ case p.currSeries.Has(quantileLabel):
+ typ = model.MetricTypeSummary
+ p.setQuantile()
+ }
+
+ p.setMetricFamilyByName(name)
+ if p.currMF.typ == "" || p.currMF.typ == model.MetricTypeUnknown {
+ p.currMF.typ = typ
+ }
+}
+
+func (p *promTextParser) setQuantile() {
+ if lbs, v, ok := removeLabel(p.currSeries, quantileLabel); ok {
+ p.isQuantile = true
+ p.currSeries = lbs
+ p.currQuantile, _ = strconv.ParseFloat(v, 64)
+ }
+}
+
+func (p *promTextParser) setBucket() {
+ if lbs, v, ok := removeLabel(p.currSeries, bucketLabel); ok {
+ p.isBucket = true
+ p.currSeries = lbs
+ p.currBucket, _ = strconv.ParseFloat(v, 64)
+ }
+}
+
+func (p *promTextParser) addGauge(value float64) {
+ p.currSeries = p.currSeries[1:] // remove "__name__"
+
+ if v := len(p.currMF.metrics); v == cap(p.currMF.metrics) {
+ p.currMF.metrics = append(p.currMF.metrics, Metric{
+ labels: copyLabels(p.currSeries),
+ gauge: &Gauge{value: value},
+ })
+ } else {
+ p.currMF.metrics = p.currMF.metrics[:v+1]
+ if p.currMF.metrics[v].gauge == nil {
+ p.currMF.metrics[v].gauge = &Gauge{}
+ }
+ p.currMF.metrics[v].gauge.value = value
+ p.currMF.metrics[v].labels = p.currMF.metrics[v].labels[:0]
+ p.currMF.metrics[v].labels = append(p.currMF.metrics[v].labels, p.currSeries...)
+ }
+}
+
+func (p *promTextParser) addCounter(value float64) {
+ p.currSeries = p.currSeries[1:] // remove "__name__"
+
+ if v := len(p.currMF.metrics); v == cap(p.currMF.metrics) {
+ p.currMF.metrics = append(p.currMF.metrics, Metric{
+ labels: copyLabels(p.currSeries),
+ counter: &Counter{value: value},
+ })
+ } else {
+ p.currMF.metrics = p.currMF.metrics[:v+1]
+ if p.currMF.metrics[v].counter == nil {
+ p.currMF.metrics[v].counter = &Counter{}
+ }
+ p.currMF.metrics[v].counter.value = value
+ p.currMF.metrics[v].labels = p.currMF.metrics[v].labels[:0]
+ p.currMF.metrics[v].labels = append(p.currMF.metrics[v].labels, p.currSeries...)
+ }
+}
+
+func (p *promTextParser) addUnknown(value float64) {
+ p.currSeries = p.currSeries[1:] // remove "__name__"
+
+ if v := len(p.currMF.metrics); v == cap(p.currMF.metrics) {
+ p.currMF.metrics = append(p.currMF.metrics, Metric{
+ labels: copyLabels(p.currSeries),
+ untyped: &Untyped{value: value},
+ })
+ } else {
+ p.currMF.metrics = p.currMF.metrics[:v+1]
+ if p.currMF.metrics[v].untyped == nil {
+ p.currMF.metrics[v].untyped = &Untyped{}
+ }
+ p.currMF.metrics[v].untyped.value = value
+ p.currMF.metrics[v].labels = p.currMF.metrics[v].labels[:0]
+ p.currMF.metrics[v].labels = append(p.currMF.metrics[v].labels, p.currSeries...)
+ }
+}
+
+func (p *promTextParser) addSummary(value float64) {
+ hash := p.currSeries.Hash()
+
+ p.currSeries = p.currSeries[1:] // remove "__name__"
+
+ s, ok := p.summaries[hash]
+ if !ok {
+ if v := len(p.currMF.metrics); v == cap(p.currMF.metrics) {
+ s = &Summary{}
+ p.currMF.metrics = append(p.currMF.metrics, Metric{
+ labels: copyLabels(p.currSeries),
+ summary: s,
+ })
+ } else {
+ p.currMF.metrics = p.currMF.metrics[:v+1]
+ if p.currMF.metrics[v].summary == nil {
+ p.currMF.metrics[v].summary = &Summary{}
+ }
+ p.currMF.metrics[v].summary.sum = 0
+ p.currMF.metrics[v].summary.count = 0
+ p.currMF.metrics[v].summary.quantiles = p.currMF.metrics[v].summary.quantiles[:0]
+ p.currMF.metrics[v].labels = p.currMF.metrics[v].labels[:0]
+ p.currMF.metrics[v].labels = append(p.currMF.metrics[v].labels, p.currSeries...)
+ s = p.currMF.metrics[v].summary
+ }
+
+ p.summaries[hash] = s
+ }
+
+ switch {
+ case p.isQuantile:
+ s.quantiles = append(s.quantiles, Quantile{quantile: p.currQuantile, value: value})
+ case p.isSum:
+ s.sum = value
+ case p.isCount:
+ s.count = value
+ }
+}
+
+func (p *promTextParser) addHistogram(value float64) {
+ hash := p.currSeries.Hash()
+
+ p.currSeries = p.currSeries[1:] // remove "__name__"
+
+ h, ok := p.histograms[hash]
+ if !ok {
+ if v := len(p.currMF.metrics); v == cap(p.currMF.metrics) {
+ h = &Histogram{}
+ p.currMF.metrics = append(p.currMF.metrics, Metric{
+ labels: copyLabels(p.currSeries),
+ histogram: h,
+ })
+ } else {
+ p.currMF.metrics = p.currMF.metrics[:v+1]
+ if p.currMF.metrics[v].histogram == nil {
+ p.currMF.metrics[v].histogram = &Histogram{}
+ }
+ p.currMF.metrics[v].histogram.sum = 0
+ p.currMF.metrics[v].histogram.count = 0
+ p.currMF.metrics[v].histogram.buckets = p.currMF.metrics[v].histogram.buckets[:0]
+ p.currMF.metrics[v].labels = p.currMF.metrics[v].labels[:0]
+ p.currMF.metrics[v].labels = append(p.currMF.metrics[v].labels, p.currSeries...)
+ h = p.currMF.metrics[v].histogram
+ }
+
+ p.histograms[hash] = h
+ }
+
+ switch {
+ case p.isBucket:
+ h.buckets = append(h.buckets, Bucket{upperBound: p.currBucket, cumulativeCount: value})
+ case p.isSum:
+ h.sum = value
+ case p.isCount:
+ h.count = value
+ }
+}
+
+func (p *promTextParser) reset() {
+ p.currMF = nil
+ p.currSeries = p.currSeries[:0]
+
+ if p.metrics == nil {
+ p.metrics = make(MetricFamilies)
+ }
+ for _, mf := range p.metrics {
+ mf.help = ""
+ mf.typ = ""
+ mf.metrics = mf.metrics[:0]
+ }
+
+ if p.summaries == nil {
+ p.summaries = make(map[uint64]*Summary)
+ }
+ for k := range p.summaries {
+ delete(p.summaries, k)
+ }
+
+ if p.histograms == nil {
+ p.histograms = make(map[uint64]*Histogram)
+ }
+ for k := range p.histograms {
+ delete(p.histograms, k)
+ }
+}
+
+func copyLabels(lbs []labels.Label) []labels.Label {
+ return append([]labels.Label(nil), lbs...)
+}
+
+func removeLabel(lbs labels.Labels, name string) (labels.Labels, string, bool) {
+ for i, v := range lbs {
+ if v.Name == name {
+ return append(lbs[:i], lbs[i+1:]...), v.Value, true
+ }
+ }
+ return lbs, "", false
+}
+
+func isSummaryOrHistogram(typ model.MetricType) bool {
+ return typ == model.MetricTypeSummary || typ == model.MetricTypeHistogram
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/parse_test.go b/src/go/collectors/go.d.plugin/pkg/prometheus/parse_test.go
new file mode 100644
index 000000000..453011c07
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/parse_test.go
@@ -0,0 +1,1675 @@
+package prometheus
+
+import (
+ "bytes"
+ "fmt"
+ "math"
+ "os"
+ "testing"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/prometheus/selector"
+
+ "github.com/prometheus/common/model"
+ "github.com/prometheus/prometheus/model/labels"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ dataMultilineHelp, _ = os.ReadFile("testdata/multiline-help.txt")
+
+ dataGaugeMeta, _ = os.ReadFile("testdata/gauge-meta.txt")
+ dataGaugeNoMeta, _ = os.ReadFile("testdata/gauge-no-meta.txt")
+ dataCounterMeta, _ = os.ReadFile("testdata/counter-meta.txt")
+ dataCounterNoMeta, _ = os.ReadFile("testdata/counter-no-meta.txt")
+ dataSummaryMeta, _ = os.ReadFile("testdata/summary-meta.txt")
+ dataSummaryNoMeta, _ = os.ReadFile("testdata/summary-no-meta.txt")
+ dataHistogramMeta, _ = os.ReadFile("testdata/histogram-meta.txt")
+ dataHistogramNoMeta, _ = os.ReadFile("testdata/histogram-no-meta.txt")
+ dataAllTypes = joinData(
+ dataGaugeMeta, dataGaugeNoMeta, dataCounterMeta, dataCounterNoMeta,
+ dataSummaryMeta, dataSummaryNoMeta, dataHistogramMeta, dataHistogramNoMeta,
+ )
+)
+
+func Test_testParseDataIsValid(t *testing.T) {
+ for name, data := range map[string][]byte{
+ "dataMultilineHelp": dataMultilineHelp,
+ "dataGaugeMeta": dataGaugeMeta,
+ "dataGaugeNoMeta": dataGaugeNoMeta,
+ "dataCounterMeta": dataCounterMeta,
+ "dataCounterNoMeta": dataCounterNoMeta,
+ "dataSummaryMeta": dataSummaryMeta,
+ "dataSummaryNoMeta": dataSummaryNoMeta,
+ "dataHistogramMeta": dataHistogramMeta,
+ "dataHistogramNoMeta": dataHistogramNoMeta,
+ "dataAllTypes": dataAllTypes,
+ } {
+ require.NotNilf(t, data, name)
+ }
+}
+
+func TestPromTextParser_parseToMetricFamilies(t *testing.T) {
+ tests := map[string]struct {
+ input []byte
+ want MetricFamilies
+ }{
+ "Gauge with multiline HELP": {
+ input: dataMultilineHelp,
+ want: MetricFamilies{
+ "test_gauge_metric_1": {
+ name: "test_gauge_metric_1",
+ help: "First line. Second line.",
+ typ: model.MetricTypeGauge,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ gauge: &Gauge{value: 11},
+ },
+ },
+ },
+ },
+ },
+ "Gauge with meta parsed as Gauge": {
+ input: dataGaugeMeta,
+ want: MetricFamilies{
+ "test_gauge_metric_1": {
+ name: "test_gauge_metric_1",
+ help: "Test Gauge Metric 1",
+ typ: model.MetricTypeGauge,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ gauge: &Gauge{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ gauge: &Gauge{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ gauge: &Gauge{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ gauge: &Gauge{value: 14},
+ },
+ },
+ },
+ "test_gauge_metric_2": {
+ name: "test_gauge_metric_2",
+ typ: model.MetricTypeGauge,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ gauge: &Gauge{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ gauge: &Gauge{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ gauge: &Gauge{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ gauge: &Gauge{value: 14},
+ },
+ },
+ },
+ },
+ },
+ "Counter with meta parsed as Counter": {
+ input: dataCounterMeta,
+ want: MetricFamilies{
+ "test_counter_metric_1_total": {
+ name: "test_counter_metric_1_total",
+ help: "Test Counter Metric 1",
+ typ: model.MetricTypeCounter,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ counter: &Counter{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ counter: &Counter{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ counter: &Counter{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ counter: &Counter{value: 14},
+ },
+ },
+ },
+ "test_counter_metric_2_total": {
+ name: "test_counter_metric_2_total",
+ typ: model.MetricTypeCounter,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ counter: &Counter{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ counter: &Counter{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ counter: &Counter{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ counter: &Counter{value: 14},
+ },
+ },
+ },
+ },
+ },
+ "Summary with meta parsed as Summary": {
+ input: dataSummaryMeta,
+ want: MetricFamilies{
+ "test_summary_1_duration_microseconds": {
+ name: "test_summary_1_duration_microseconds",
+ help: "Test Summary Metric 1",
+ typ: model.MetricTypeSummary,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ },
+ },
+ "test_summary_2_duration_microseconds": {
+ name: "test_summary_2_duration_microseconds",
+ typ: model.MetricTypeSummary,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "Histogram with meta parsed as Histogram": {
+ input: dataHistogramMeta,
+ want: MetricFamilies{
+ "test_histogram_1_duration_seconds": {
+ name: "test_histogram_1_duration_seconds",
+ help: "Test Histogram Metric 1",
+ typ: model.MetricTypeHistogram,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ },
+ },
+ "test_histogram_2_duration_seconds": {
+ name: "test_histogram_2_duration_seconds",
+ typ: model.MetricTypeHistogram,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "Gauge no meta parsed as Untyped": {
+ input: dataGaugeNoMeta,
+ want: MetricFamilies{
+ "test_gauge_no_meta_metric_1": {
+ name: "test_gauge_no_meta_metric_1",
+ typ: model.MetricTypeUnknown,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ untyped: &Untyped{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ untyped: &Untyped{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ untyped: &Untyped{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ untyped: &Untyped{value: 14},
+ },
+ },
+ },
+ "test_gauge_no_meta_metric_2": {
+ name: "test_gauge_no_meta_metric_2",
+ typ: model.MetricTypeUnknown,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ untyped: &Untyped{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ untyped: &Untyped{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ untyped: &Untyped{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ untyped: &Untyped{value: 14},
+ },
+ },
+ },
+ },
+ },
+ "Counter no meta parsed as Untyped": {
+ input: dataCounterNoMeta,
+ want: MetricFamilies{
+ "test_counter_no_meta_metric_1_total": {
+ name: "test_counter_no_meta_metric_1_total",
+ typ: model.MetricTypeUnknown,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ untyped: &Untyped{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ untyped: &Untyped{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ untyped: &Untyped{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ untyped: &Untyped{value: 14},
+ },
+ },
+ },
+ "test_counter_no_meta_metric_2_total": {
+ name: "test_counter_no_meta_metric_2_total",
+ typ: model.MetricTypeUnknown,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ untyped: &Untyped{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ untyped: &Untyped{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ untyped: &Untyped{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ untyped: &Untyped{value: 14},
+ },
+ },
+ },
+ },
+ },
+ "Summary no meta parsed as Summary": {
+ input: dataSummaryNoMeta,
+ want: MetricFamilies{
+ "test_summary_no_meta_1_duration_microseconds": {
+ name: "test_summary_no_meta_1_duration_microseconds",
+ typ: model.MetricTypeSummary,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ },
+ },
+ "test_summary_no_meta_2_duration_microseconds": {
+ name: "test_summary_no_meta_2_duration_microseconds",
+ typ: model.MetricTypeSummary,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "Histogram no meta parsed as Histogram": {
+ input: dataHistogramNoMeta,
+ want: MetricFamilies{
+ "test_histogram_no_meta_1_duration_seconds": {
+ name: "test_histogram_no_meta_1_duration_seconds",
+ typ: model.MetricTypeHistogram,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ },
+ },
+ "test_histogram_no_meta_2_duration_seconds": {
+ name: "test_histogram_no_meta_2_duration_seconds",
+ typ: model.MetricTypeHistogram,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "All types": {
+ input: dataAllTypes,
+ want: MetricFamilies{
+ "test_gauge_metric_1": {
+ name: "test_gauge_metric_1",
+ help: "Test Gauge Metric 1",
+ typ: model.MetricTypeGauge,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ gauge: &Gauge{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ gauge: &Gauge{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ gauge: &Gauge{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ gauge: &Gauge{value: 14},
+ },
+ },
+ },
+ "test_gauge_metric_2": {
+ name: "test_gauge_metric_2",
+ typ: model.MetricTypeGauge,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ gauge: &Gauge{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ gauge: &Gauge{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ gauge: &Gauge{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ gauge: &Gauge{value: 14},
+ },
+ },
+ },
+ "test_counter_metric_1_total": {
+ name: "test_counter_metric_1_total",
+ help: "Test Counter Metric 1",
+ typ: model.MetricTypeCounter,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ counter: &Counter{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ counter: &Counter{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ counter: &Counter{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ counter: &Counter{value: 14},
+ },
+ },
+ },
+ "test_counter_metric_2_total": {
+ name: "test_counter_metric_2_total",
+ typ: model.MetricTypeCounter,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ counter: &Counter{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ counter: &Counter{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ counter: &Counter{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ counter: &Counter{value: 14},
+ },
+ },
+ },
+ "test_summary_1_duration_microseconds": {
+ name: "test_summary_1_duration_microseconds",
+ help: "Test Summary Metric 1",
+ typ: model.MetricTypeSummary,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ },
+ },
+ "test_summary_2_duration_microseconds": {
+ name: "test_summary_2_duration_microseconds",
+ typ: model.MetricTypeSummary,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ },
+ },
+ "test_histogram_1_duration_seconds": {
+ name: "test_histogram_1_duration_seconds",
+ help: "Test Histogram Metric 1",
+ typ: model.MetricTypeHistogram,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ },
+ },
+ "test_histogram_2_duration_seconds": {
+ name: "test_histogram_2_duration_seconds",
+ typ: model.MetricTypeHistogram,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ },
+ },
+ "test_gauge_no_meta_metric_1": {
+ name: "test_gauge_no_meta_metric_1",
+ typ: model.MetricTypeUnknown,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ untyped: &Untyped{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ untyped: &Untyped{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ untyped: &Untyped{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ untyped: &Untyped{value: 14},
+ },
+ },
+ },
+ "test_gauge_no_meta_metric_2": {
+ name: "test_gauge_no_meta_metric_2",
+ typ: model.MetricTypeUnknown,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ untyped: &Untyped{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ untyped: &Untyped{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ untyped: &Untyped{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ untyped: &Untyped{value: 14},
+ },
+ },
+ },
+ "test_counter_no_meta_metric_1_total": {
+ name: "test_counter_no_meta_metric_1_total",
+ typ: model.MetricTypeUnknown,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ untyped: &Untyped{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ untyped: &Untyped{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ untyped: &Untyped{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ untyped: &Untyped{value: 14},
+ },
+ },
+ },
+ "test_counter_no_meta_metric_2_total": {
+ name: "test_counter_no_meta_metric_2_total",
+ typ: model.MetricTypeUnknown,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ untyped: &Untyped{value: 11},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ untyped: &Untyped{value: 12},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ untyped: &Untyped{value: 13},
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ untyped: &Untyped{value: 14},
+ },
+ },
+ },
+ "test_summary_no_meta_1_duration_microseconds": {
+ name: "test_summary_no_meta_1_duration_microseconds",
+ typ: model.MetricTypeSummary,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ summary: &Summary{
+ sum: 283201.29,
+ count: 31,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 4931.921},
+ {quantile: 0.9, value: 4932.921},
+ {quantile: 0.99, value: 4933.921},
+ },
+ },
+ },
+ },
+ },
+ "test_summary_no_meta_2_duration_microseconds": {
+ name: "test_summary_no_meta_2_duration_microseconds",
+ typ: model.MetricTypeSummary,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ summary: &Summary{
+ sum: 383201.29,
+ count: 41,
+ quantiles: []Quantile{
+ {quantile: 0.5, value: 5931.921},
+ {quantile: 0.9, value: 5932.921},
+ {quantile: 0.99, value: 5933.921},
+ },
+ },
+ },
+ },
+ },
+ "test_histogram_no_meta_1_duration_seconds": {
+ name: "test_histogram_no_meta_1_duration_seconds",
+ typ: model.MetricTypeHistogram,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ histogram: &Histogram{
+ sum: 0.00147889,
+ count: 6,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 4},
+ {upperBound: 0.5, cumulativeCount: 5},
+ {upperBound: math.Inf(1), cumulativeCount: 6},
+ },
+ },
+ },
+ },
+ },
+ "test_histogram_no_meta_2_duration_seconds": {
+ name: "test_histogram_no_meta_2_duration_seconds",
+ typ: model.MetricTypeHistogram,
+ metrics: []Metric{
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value1"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value2"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value3"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ {
+ labels: labels.Labels{{Name: "label1", Value: "value4"}},
+ histogram: &Histogram{
+ sum: 0.00247889,
+ count: 9,
+ buckets: []Bucket{
+ {upperBound: 0.1, cumulativeCount: 7},
+ {upperBound: 0.5, cumulativeCount: 8},
+ {upperBound: math.Inf(1), cumulativeCount: 9},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ var p promTextParser
+
+ for i := 0; i < 10; i++ {
+ t.Run(fmt.Sprintf("parse num %d", i+1), func(t *testing.T) {
+ mfs, err := p.parseToMetricFamilies(test.input)
+ if len(test.want) > 0 {
+ assert.Equal(t, test.want, mfs)
+ } else {
+ assert.Error(t, err)
+ }
+ })
+ }
+ })
+ }
+}
+
+func TestPromTextParser_parseToMetricFamiliesWithSelector(t *testing.T) {
+ sr, err := selector.Parse(`test_gauge_metric_1{label1="value2"}`)
+ require.NoError(t, err)
+
+ p := promTextParser{sr: sr}
+
+ txt := []byte(`
+test_gauge_metric_1{label1="value1"} 1
+test_gauge_metric_1{label1="value2"} 1
+test_gauge_metric_2{label1="value1"} 1
+test_gauge_metric_2{label1="value2"} 1
+`)
+
+ want := MetricFamilies{
+ "test_gauge_metric_1": &MetricFamily{
+ name: "test_gauge_metric_1",
+ typ: model.MetricTypeUnknown,
+ metrics: []Metric{
+ {labels: labels.Labels{{Name: "label1", Value: "value2"}}, untyped: &Untyped{value: 1}},
+ },
+ },
+ }
+
+ mfs, err := p.parseToMetricFamilies(txt)
+
+ require.NoError(t, err)
+ assert.Equal(t, want, mfs)
+}
+
+func TestPromTextParser_parseToSeries(t *testing.T) {
+ tests := map[string]struct {
+ input []byte
+ want Series
+ }{
+ "All types": {
+ input: []byte(`
+# HELP test_gauge_metric_1 Test Gauge Metric 1
+# TYPE test_gauge_metric_1 gauge
+test_gauge_metric_1{label1="value1"} 11
+test_gauge_no_meta_metric_1{label1="value1"} 11
+# HELP test_counter_metric_1_total Test Counter Metric 1
+# TYPE test_counter_metric_1_total counter
+test_counter_metric_1_total{label1="value1"} 11
+test_counter_no_meta_metric_1_total{label1="value1"} 11
+# HELP test_summary_1_duration_microseconds Test Summary Metric 1
+# TYPE test_summary_1_duration_microseconds summary
+test_summary_1_duration_microseconds{label1="value1",quantile="0.5"} 4931.921
+test_summary_1_duration_microseconds{label1="value1",quantile="0.9"} 4932.921
+test_summary_1_duration_microseconds{label1="value1",quantile="0.99"} 4933.921
+test_summary_1_duration_microseconds_sum{label1="value1"} 283201.29
+test_summary_1_duration_microseconds_count{label1="value1"} 31
+test_summary_no_meta_1_duration_microseconds{label1="value1",quantile="0.5"} 4931.921
+test_summary_no_meta_1_duration_microseconds{label1="value1",quantile="0.9"} 4932.921
+test_summary_no_meta_1_duration_microseconds{label1="value1",quantile="0.99"} 4933.921
+test_summary_no_meta_1_duration_microseconds_sum{label1="value1"} 283201.29
+test_summary_no_meta_1_duration_microseconds_count{label1="value1"} 31
+# HELP test_histogram_1_duration_seconds Test Histogram Metric 1
+# TYPE test_histogram_1_duration_seconds histogram
+test_histogram_1_duration_seconds_bucket{label1="value1",le="0.1"} 4
+test_histogram_1_duration_seconds_bucket{label1="value1",le="0.5"} 5
+test_histogram_1_duration_seconds_bucket{label1="value1",le="+Inf"} 6
+test_histogram_1_duration_seconds_sum{label1="value1"} 0.00147889
+test_histogram_1_duration_seconds_count{label1="value1"} 6
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value1",le="0.1"} 4
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value1",le="0.5"} 5
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value1",le="+Inf"} 6
+test_histogram_no_meta_1_duration_seconds_sum{label1="value1"} 0.00147889
+test_histogram_no_meta_1_duration_seconds_count{label1="value1"} 6
+`),
+ want: Series{
+ // Gauge
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_gauge_metric_1"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 11,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_gauge_no_meta_metric_1"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 11,
+ },
+ // Counter
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_counter_metric_1_total"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 11,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_counter_no_meta_metric_1_total"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 11,
+ },
+ //// Summary
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_summary_1_duration_microseconds"},
+ {Name: "label1", Value: "value1"},
+ {Name: "quantile", Value: "0.5"},
+ },
+ Value: 4931.921,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_summary_1_duration_microseconds"},
+ {Name: "label1", Value: "value1"},
+ {Name: "quantile", Value: "0.9"},
+ },
+ Value: 4932.921,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_summary_1_duration_microseconds"},
+ {Name: "label1", Value: "value1"},
+ {Name: "quantile", Value: "0.99"},
+ },
+ Value: 4933.921,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_summary_1_duration_microseconds_sum"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 283201.29,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_summary_1_duration_microseconds_count"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 31,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_summary_no_meta_1_duration_microseconds"},
+ {Name: "label1", Value: "value1"},
+ {Name: "quantile", Value: "0.5"},
+ },
+ Value: 4931.921,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_summary_no_meta_1_duration_microseconds"},
+ {Name: "label1", Value: "value1"},
+ {Name: "quantile", Value: "0.9"},
+ },
+ Value: 4932.921,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_summary_no_meta_1_duration_microseconds"},
+ {Name: "label1", Value: "value1"},
+ {Name: "quantile", Value: "0.99"},
+ },
+ Value: 4933.921,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_summary_no_meta_1_duration_microseconds_sum"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 283201.29,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_summary_no_meta_1_duration_microseconds_count"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 31,
+ },
+ // Histogram
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_histogram_1_duration_seconds_bucket"},
+ {Name: "label1", Value: "value1"},
+ {Name: "le", Value: "0.1"},
+ },
+ Value: 4,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_histogram_1_duration_seconds_bucket"},
+ {Name: "label1", Value: "value1"},
+ {Name: "le", Value: "0.5"},
+ },
+ Value: 5,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_histogram_1_duration_seconds_bucket"},
+ {Name: "label1", Value: "value1"},
+ {Name: "le", Value: "+Inf"},
+ },
+ Value: 6,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_histogram_1_duration_seconds_sum"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 0.00147889,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_histogram_1_duration_seconds_count"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 6,
+ },
+
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_histogram_no_meta_1_duration_seconds_bucket"},
+ {Name: "label1", Value: "value1"},
+ {Name: "le", Value: "0.1"},
+ },
+ Value: 4,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_histogram_no_meta_1_duration_seconds_bucket"},
+ {Name: "label1", Value: "value1"},
+ {Name: "le", Value: "0.5"},
+ },
+ Value: 5,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_histogram_no_meta_1_duration_seconds_bucket"},
+ {Name: "label1", Value: "value1"},
+ {Name: "le", Value: "+Inf"},
+ },
+ Value: 6,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_histogram_no_meta_1_duration_seconds_sum"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 0.00147889,
+ },
+ {
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_histogram_no_meta_1_duration_seconds_count"},
+ {Name: "label1", Value: "value1"},
+ },
+ Value: 6,
+ },
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ var p promTextParser
+
+ for i := 0; i < 10; i++ {
+ t.Run(fmt.Sprintf("parse num %d", i+1), func(t *testing.T) {
+ series, err := p.parseToSeries(test.input)
+
+ if len(test.want) > 0 {
+ test.want.Sort()
+ assert.Equal(t, test.want, series)
+ } else {
+ assert.Error(t, err)
+ }
+ })
+ }
+ })
+ }
+}
+
+func TestPromTextParser_parseToSeriesWithSelector(t *testing.T) {
+ sr, err := selector.Parse(`test_gauge_metric_1{label1="value2"}`)
+ require.NoError(t, err)
+
+ p := promTextParser{sr: sr}
+
+ txt := []byte(`
+test_gauge_metric_1{label1="value1"} 1
+test_gauge_metric_1{label1="value2"} 1
+test_gauge_metric_2{label1="value1"} 1
+test_gauge_metric_2{label1="value2"} 1
+`)
+
+ want := Series{SeriesSample{
+ Labels: labels.Labels{
+ {Name: "__name__", Value: "test_gauge_metric_1"},
+ {Name: "label1", Value: "value2"},
+ },
+ Value: 1,
+ }}
+
+ series, err := p.parseToSeries(txt)
+
+ require.NoError(t, err)
+ assert.Equal(t, want, series)
+}
+
+func joinData(data ...[]byte) []byte {
+ var buf bytes.Buffer
+ for _, v := range data {
+ _, _ = buf.Write(v)
+ _ = buf.WriteByte('\n')
+ }
+ return buf.Bytes()
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/selector/README.md b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/README.md
new file mode 100644
index 000000000..75682c38d
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/README.md
@@ -0,0 +1,102 @@
+<!--
+title: "Time series selector"
+custom_edit_url: "/src/go/collectors/go.d.plugin/pkg/prometheus/selector/README.md"
+sidebar_label: "Time series selector"
+learn_status: "Published"
+learn_rel_path: "Developers/External plugins/go.d.plugin/Helper Packages"
+-->
+
+# Time series selector
+
+Selectors allow selecting and filtering of a set of time series.
+
+## Simple Selector
+
+In the simplest form you need to specify only a metric name.
+
+### Syntax
+
+```cmd
+ <line> ::= <metric_name_pattern>
+ <metric_name_pattern> ::= simple pattern
+```
+
+The metric name pattern syntax is [simple pattern](/src/libnetdata/simple_pattern/README.md).
+
+### Examples
+
+This example selects all time series that have the `go_memstats_alloc_bytes` metric name:
+
+```cmd
+go_memstats_alloc_bytes
+```
+
+This example selects all time series with metric names starts with `go_memstats_`:
+
+```cmd
+go_memstats_*
+```
+
+This example selects all time series with metric names starts with `go_` except `go_memstats_`:
+
+```cmd
+!go_memstats_* go_*
+```
+
+## Advanced Selector
+
+It is possible to filter these time series further by appending a comma separated list of label matchers in curly braces (`{}`).
+
+### Syntax
+
+```cmd
+ <line> ::= [ <metric_name_pattern> ]{ <list_of_selectors> }
+ <metric_name_pattern> ::= simple pattern
+ <list_of_selectors> ::= a comma separated list <label_name><op><label_value_pattern>
+ <label_name> ::= an exact label name
+ <op> ::= [ '=', '!=', '=~', '!~', '=*', '!*' ]
+ <label_value_pattern> ::= a label value pattern, depends on <op>
+```
+
+The metric name pattern syntax is [simple pattern](/src/libnetdata/simple_pattern/README.md).
+
+Label matching operators:
+
+- `=`: Match labels that are exactly equal to the provided string.
+- `!=`: Match labels that are not equal to the provided string.
+- `=~`: Match labels that [regex-match](https://golang.org/pkg/regexp/syntax/) the provided string.
+- `!~`: Match labels that do not [regex-match](https://golang.org/pkg/regexp/syntax/) the provided string.
+- `=*`: Match labels that [simple-pattern-match](/src/libnetdata/simple_pattern/README.md) the provided string.
+- `!*`: Match labels that do not [simple-pattern-match](/src/libnetdata/simple_pattern/README.md) the provided string.
+
+### Examples
+
+This example selects all time series that:
+
+- have the `node_cooling_device_cur_state` metric name and
+- label `type` value not equal to `Fan`:
+
+```cmd
+node_cooling_device_cur_state{type!="Fan"}
+```
+
+This example selects all time series that:
+
+- have the `node_filesystem_size_bytes` metric name and
+- label `device` value is either `/dev/nvme0n1p1` or `/dev/nvme0n1p2` and
+- label `fstype` is equal to `ext4`
+
+```cmd
+node_filesystem_size_bytes{device=~"/dev/nvme0n1p1$|/dev/nvme0n1p2$",fstype="ext4"}
+```
+
+Label matchers can also be applied to metric names by matching against the internal `__name__` label.
+
+For example, the expression `node_filesystem_size_bytes` is equivalent to `{__name__="node_filesystem_size_bytes"}`.
+This allows using all operators (other than `=*`) for metric names matching.
+
+The following expression selects all metrics that have a name starting with `node_`:
+
+```cmd
+{__name__=*"node_*"}
+```
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/selector/expr.go b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/expr.go
new file mode 100644
index 000000000..6f61cf3a5
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/expr.go
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package selector
+
+import "fmt"
+
+type Expr struct {
+ Allow []string `yaml:"allow,omitempty" json:"allow"`
+ Deny []string `yaml:"deny,omitempty" json:"deny"`
+}
+
+func (e Expr) Empty() bool {
+ return len(e.Allow) == 0 && len(e.Deny) == 0
+
+}
+
+func (e Expr) Parse() (Selector, error) {
+ if e.Empty() {
+ return nil, nil
+ }
+
+ var srs []Selector
+ var allow Selector
+ var deny Selector
+
+ for _, item := range e.Allow {
+ sr, err := Parse(item)
+ if err != nil {
+ return nil, fmt.Errorf("parse selector '%s': %v", item, err)
+ }
+ srs = append(srs, sr)
+ }
+
+ switch len(srs) {
+ case 0:
+ allow = trueSelector{}
+ case 1:
+ allow = srs[0]
+ default:
+ allow = Or(srs[0], srs[1], srs[2:]...)
+ }
+
+ srs = srs[:0]
+ for _, item := range e.Deny {
+ sr, err := Parse(item)
+ if err != nil {
+ return nil, fmt.Errorf("parse selector '%s': %v", item, err)
+ }
+ srs = append(srs, sr)
+ }
+
+ switch len(srs) {
+ case 0:
+ deny = falseSelector{}
+ case 1:
+ deny = srs[0]
+ default:
+ deny = Or(srs[0], srs[1], srs[2:]...)
+ }
+
+ return And(allow, Not(deny)), nil
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/selector/expr_test.go b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/expr_test.go
new file mode 100644
index 000000000..598cef9b8
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/expr_test.go
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package selector
+
+import (
+ "testing"
+
+ "github.com/prometheus/prometheus/model/labels"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestExpr_Empty(t *testing.T) {
+ tests := map[string]struct {
+ expr Expr
+ expected bool
+ }{
+ "empty: both allow and deny": {
+ expr: Expr{
+ Allow: []string{},
+ Deny: []string{},
+ },
+ expected: true,
+ },
+ "nil: both allow and deny": {
+ expected: true,
+ },
+ "nil, empty: allow, deny": {
+ expr: Expr{
+ Deny: []string{""},
+ },
+ expected: false,
+ },
+ "empty, nil: allow, deny": {
+ expr: Expr{
+ Allow: []string{""},
+ },
+ expected: false,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ if test.expected {
+ assert.True(t, test.expr.Empty())
+ } else {
+ assert.False(t, test.expr.Empty())
+ }
+ })
+ }
+}
+
+func TestExpr_Parse(t *testing.T) {
+ tests := map[string]struct {
+ expr Expr
+ expectedSr Selector
+ expectedErr bool
+ }{
+ "not set: both allow and deny": {
+ expr: Expr{},
+ },
+ "set: both allow and deny": {
+ expr: Expr{
+ Allow: []string{
+ "go_memstats_*",
+ "node_*",
+ },
+ Deny: []string{
+ "go_memstats_frees_total",
+ "node_cooling_*",
+ },
+ },
+ expectedSr: andSelector{
+ lhs: orSelector{
+ lhs: mustSPName("go_memstats_*"),
+ rhs: mustSPName("node_*"),
+ },
+ rhs: Not(orSelector{
+ lhs: mustSPName("go_memstats_frees_total"),
+ rhs: mustSPName("node_cooling_*"),
+ }),
+ },
+ },
+ "set: only includes": {
+ expr: Expr{
+ Allow: []string{
+ "go_memstats_*",
+ "node_*",
+ },
+ },
+ expectedSr: andSelector{
+ lhs: orSelector{
+ lhs: mustSPName("go_memstats_*"),
+ rhs: mustSPName("node_*"),
+ },
+ rhs: Not(falseSelector{}),
+ },
+ },
+ "set: only excludes": {
+ expr: Expr{
+ Deny: []string{
+ "go_memstats_frees_total",
+ "node_cooling_*",
+ },
+ },
+ expectedSr: andSelector{
+ lhs: trueSelector{},
+ rhs: Not(orSelector{
+ lhs: mustSPName("go_memstats_frees_total"),
+ rhs: mustSPName("node_cooling_*"),
+ }),
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ m, err := test.expr.Parse()
+
+ if test.expectedErr {
+ assert.Error(t, err)
+ } else {
+ assert.Equal(t, test.expectedSr, m)
+ }
+ })
+ }
+}
+
+func TestExprSelector_Matches(t *testing.T) {
+ tests := map[string]struct {
+ expr Expr
+ lbs labels.Labels
+ expectedMatches bool
+ }{
+ "allow matches: single pattern": {
+ expr: Expr{
+ Allow: []string{"go_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: true,
+ },
+ "allow matches: several patterns": {
+ expr: Expr{
+ Allow: []string{"node_*", "go_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: true,
+ },
+ "allow not matches": {
+ expr: Expr{
+ Allow: []string{"node_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: false,
+ },
+ "deny matches: single pattern": {
+ expr: Expr{
+ Deny: []string{"go_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: false,
+ },
+ "deny matches: several patterns": {
+ expr: Expr{
+ Deny: []string{"node_*", "go_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: false,
+ },
+ "deny not matches": {
+ expr: Expr{
+ Deny: []string{"node_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: true,
+ },
+ "allow and deny matches: single pattern": {
+ expr: Expr{
+ Allow: []string{"go_*"},
+ Deny: []string{"go_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: false,
+ },
+ "allow and deny matches: several patterns": {
+ expr: Expr{
+ Allow: []string{"node_*", "go_*"},
+ Deny: []string{"node_*", "go_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: false,
+ },
+ "allow matches and deny not matches": {
+ expr: Expr{
+ Allow: []string{"go_*"},
+ Deny: []string{"node_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: true,
+ },
+ "allow not matches and deny matches": {
+ expr: Expr{
+ Allow: []string{"node_*"},
+ Deny: []string{"go_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: false,
+ },
+ "allow not matches and deny not matches": {
+ expr: Expr{
+ Allow: []string{"node_*"},
+ Deny: []string{"node_*"},
+ },
+ lbs: []labels.Label{{Name: labels.MetricName, Value: "go_memstats_alloc_bytes"}},
+ expectedMatches: false,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ sr, err := test.expr.Parse()
+ require.NoError(t, err)
+
+ if test.expectedMatches {
+ assert.True(t, sr.Matches(test.lbs))
+ } else {
+ assert.False(t, sr.Matches(test.lbs))
+ }
+ })
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/selector/logical.go b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/logical.go
new file mode 100644
index 000000000..1556d1715
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/logical.go
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package selector
+
+import (
+ "github.com/prometheus/prometheus/model/labels"
+)
+
+type (
+ trueSelector struct{}
+ falseSelector struct{}
+ negSelector struct{ s Selector }
+ andSelector struct{ lhs, rhs Selector }
+ orSelector struct{ lhs, rhs Selector }
+)
+
+func (trueSelector) Matches(_ labels.Labels) bool { return true }
+func (falseSelector) Matches(_ labels.Labels) bool { return false }
+func (s negSelector) Matches(lbs labels.Labels) bool { return !s.s.Matches(lbs) }
+func (s andSelector) Matches(lbs labels.Labels) bool { return s.lhs.Matches(lbs) && s.rhs.Matches(lbs) }
+func (s orSelector) Matches(lbs labels.Labels) bool { return s.lhs.Matches(lbs) || s.rhs.Matches(lbs) }
+
+// True returns a selector which always returns true
+func True() Selector {
+ return trueSelector{}
+}
+
+// And returns a selector which returns true only if all of it's sub-selectors return true
+func And(lhs, rhs Selector, others ...Selector) Selector {
+ s := andSelector{lhs: lhs, rhs: rhs}
+ if len(others) == 0 {
+ return s
+ }
+ return And(s, others[0], others[1:]...)
+}
+
+// Or returns a selector which returns true if any of it's sub-selectors return true
+func Or(lhs, rhs Selector, others ...Selector) Selector {
+ s := orSelector{lhs: lhs, rhs: rhs}
+ if len(others) == 0 {
+ return s
+ }
+ return Or(s, others[0], others[1:]...)
+}
+
+// Not returns a selector which returns the negation of the sub-selector's result
+func Not(s Selector) Selector {
+ return negSelector{s}
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/selector/logical_test.go b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/logical_test.go
new file mode 100644
index 000000000..239c7f715
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/logical_test.go
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package selector
+
+import (
+ "testing"
+
+ "github.com/prometheus/prometheus/model/labels"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestTrueSelector_Matches(t *testing.T) {
+ tests := map[string]struct {
+ sr trueSelector
+ lbs labels.Labels
+ expected bool
+ }{
+ "not empty labels": {
+ lbs: labels.Labels{{Name: labels.MetricName, Value: "name"}},
+ expected: true,
+ },
+ "empty labels": {
+ expected: true,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ if test.expected {
+ assert.True(t, test.sr.Matches(test.lbs))
+ } else {
+ assert.False(t, test.sr.Matches(test.lbs))
+ }
+ })
+ }
+}
+
+func TestFalseSelector_Matches(t *testing.T) {
+ tests := map[string]struct {
+ sr falseSelector
+ lbs labels.Labels
+ expected bool
+ }{
+ "not empty labels": {
+ lbs: labels.Labels{{Name: labels.MetricName, Value: "name"}},
+ expected: false,
+ },
+ "empty labels": {
+ expected: false,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ if test.expected {
+ assert.True(t, test.sr.Matches(test.lbs))
+ } else {
+ assert.False(t, test.sr.Matches(test.lbs))
+ }
+ })
+ }
+}
+
+func TestNegSelector_Matches(t *testing.T) {
+ tests := map[string]struct {
+ sr negSelector
+ lbs labels.Labels
+ expected bool
+ }{
+ "true matcher": {
+ sr: negSelector{trueSelector{}},
+ lbs: labels.Labels{{Name: labels.MetricName, Value: "name"}},
+ expected: false,
+ },
+ "false matcher": {
+ sr: negSelector{falseSelector{}},
+ lbs: labels.Labels{{Name: labels.MetricName, Value: "name"}},
+ expected: true,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ if test.expected {
+ assert.True(t, test.sr.Matches(test.lbs))
+ } else {
+ assert.False(t, test.sr.Matches(test.lbs))
+ }
+ })
+ }
+}
+
+func TestAndSelector_Matches(t *testing.T) {
+ tests := map[string]struct {
+ sr andSelector
+ lbs labels.Labels
+ expected bool
+ }{
+ "true, true": {
+ sr: andSelector{lhs: trueSelector{}, rhs: trueSelector{}},
+ expected: true,
+ },
+ "true, false": {
+ sr: andSelector{lhs: trueSelector{}, rhs: falseSelector{}},
+ expected: false,
+ },
+ "false, true": {
+ sr: andSelector{lhs: trueSelector{}, rhs: falseSelector{}},
+ expected: false,
+ },
+ "false, false": {
+ sr: andSelector{lhs: falseSelector{}, rhs: falseSelector{}},
+ expected: false,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.expected, test.sr.Matches(test.lbs))
+ })
+ }
+}
+
+func TestOrSelector_Matches(t *testing.T) {
+ tests := map[string]struct {
+ sr orSelector
+ lbs labels.Labels
+ expected bool
+ }{
+ "true, true": {
+ sr: orSelector{lhs: trueSelector{}, rhs: trueSelector{}},
+ expected: true,
+ },
+ "true, false": {
+ sr: orSelector{lhs: trueSelector{}, rhs: falseSelector{}},
+ expected: true,
+ },
+ "false, true": {
+ sr: orSelector{lhs: trueSelector{}, rhs: falseSelector{}},
+ expected: true,
+ },
+ "false, false": {
+ sr: orSelector{lhs: falseSelector{}, rhs: falseSelector{}},
+ expected: false,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ assert.Equal(t, test.expected, test.sr.Matches(test.lbs))
+ })
+ }
+}
+
+func Test_And(t *testing.T) {
+ tests := map[string]struct {
+ srs []Selector
+ expected Selector
+ }{
+ "2 selectors": {
+ srs: []Selector{trueSelector{}, trueSelector{}},
+ expected: andSelector{
+ lhs: trueSelector{},
+ rhs: trueSelector{},
+ },
+ },
+ "4 selectors": {
+ srs: []Selector{trueSelector{}, trueSelector{}, trueSelector{}, trueSelector{}},
+ expected: andSelector{
+ lhs: andSelector{
+ lhs: andSelector{
+ lhs: trueSelector{},
+ rhs: trueSelector{},
+ },
+ rhs: trueSelector{},
+ },
+ rhs: trueSelector{}},
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ require.GreaterOrEqual(t, len(test.srs), 2)
+
+ s := And(test.srs[0], test.srs[1], test.srs[2:]...)
+ assert.Equal(t, test.expected, s)
+ })
+ }
+}
+
+func Test_Or(t *testing.T) {
+ tests := map[string]struct {
+ srs []Selector
+ expected Selector
+ }{
+ "2 selectors": {
+ srs: []Selector{trueSelector{}, trueSelector{}},
+ expected: orSelector{
+ lhs: trueSelector{},
+ rhs: trueSelector{},
+ },
+ },
+ "4 selectors": {
+ srs: []Selector{trueSelector{}, trueSelector{}, trueSelector{}, trueSelector{}},
+ expected: orSelector{
+ lhs: orSelector{
+ lhs: orSelector{
+ lhs: trueSelector{},
+ rhs: trueSelector{},
+ },
+ rhs: trueSelector{},
+ },
+ rhs: trueSelector{}},
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ require.GreaterOrEqual(t, len(test.srs), 2)
+
+ s := Or(test.srs[0], test.srs[1], test.srs[2:]...)
+ assert.Equal(t, test.expected, s)
+ })
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/selector/parse.go b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/parse.go
new file mode 100644
index 000000000..29c1d4fbf
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/parse.go
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package selector
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/matcher"
+)
+
+var (
+ reLV = regexp.MustCompile(`^(?P<label_name>[a-zA-Z0-9_]+)(?P<op>=~|!~|=\*|!\*|=|!=)"(?P<pattern>.+)"$`)
+)
+
+func Parse(expr string) (Selector, error) {
+ var srs []Selector
+ lvs := strings.Split(unsugarExpr(expr), ",")
+
+ for _, lv := range lvs {
+ sr, err := parseSelector(lv)
+ if err != nil {
+ return nil, err
+ }
+ srs = append(srs, sr)
+ }
+
+ switch len(srs) {
+ case 0:
+ return nil, nil
+ case 1:
+ return srs[0], nil
+ default:
+ return And(srs[0], srs[1], srs[2:]...), nil
+ }
+}
+
+func parseSelector(line string) (Selector, error) {
+ sub := reLV.FindStringSubmatch(strings.TrimSpace(line))
+ if sub == nil {
+ return nil, fmt.Errorf("invalid selector syntax: '%s'", line)
+ }
+
+ name, op, pattern := sub[1], sub[2], strings.Trim(sub[3], "\"")
+
+ var m matcher.Matcher
+ var err error
+
+ switch op {
+ case OpEqual, OpNegEqual:
+ m, err = matcher.NewStringMatcher(pattern, true, true)
+ case OpRegexp, OpNegRegexp:
+ m, err = matcher.NewRegExpMatcher(pattern)
+ case OpSimplePatterns, OpNegSimplePatterns:
+ m, err = matcher.NewSimplePatternsMatcher(pattern)
+ default:
+ err = fmt.Errorf("unknown matching operator: %s", op)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ sr := labelSelector{
+ name: name,
+ m: m,
+ }
+
+ if neg := strings.HasPrefix(op, "!"); neg {
+ return Not(sr), nil
+ }
+ return sr, nil
+}
+
+func unsugarExpr(expr string) string {
+ // name => __name__=*"name"
+ // name{label="value"} => __name__=*"name",label="value"
+ // {label="value"} => label="value"
+ expr = strings.TrimSpace(expr)
+
+ switch idx := strings.IndexByte(expr, '{'); true {
+ case idx == -1:
+ expr = fmt.Sprintf(`__name__%s"%s"`,
+ OpSimplePatterns,
+ strings.TrimSpace(expr),
+ )
+ case idx == 0:
+ expr = strings.Trim(expr, "{}")
+ default:
+ expr = fmt.Sprintf(`__name__%s"%s",%s`,
+ OpSimplePatterns,
+ strings.TrimSpace(expr[:idx]),
+ strings.Trim(expr[idx:], "{}"),
+ )
+ }
+ return expr
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/selector/parse_test.go b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/parse_test.go
new file mode 100644
index 000000000..ba764e039
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/parse_test.go
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package selector
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/matcher"
+
+ "github.com/prometheus/prometheus/model/labels"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParse(t *testing.T) {
+ tests := map[string]struct {
+ input string
+ expectedSr Selector
+ expectedErr bool
+ }{
+ "sp op: only metric name": {
+ input: "go_memstats_alloc_bytes !go_memstats_* *",
+ expectedSr: mustSPName("go_memstats_alloc_bytes !go_memstats_* *"),
+ },
+ "string op: metric name with labels": {
+ input: fmt.Sprintf(`go_memstats_*{label%s"value"}`, OpEqual),
+ expectedSr: andSelector{
+ lhs: mustSPName("go_memstats_*"),
+ rhs: mustString("label", "value"),
+ },
+ },
+ "neg string op: metric name with labels": {
+ input: fmt.Sprintf(`go_memstats_*{label%s"value"}`, OpNegEqual),
+ expectedSr: andSelector{
+ lhs: mustSPName("go_memstats_*"),
+ rhs: Not(mustString("label", "value")),
+ },
+ },
+ "regexp op: metric name with labels": {
+ input: fmt.Sprintf(`go_memstats_*{label%s"valu.+"}`, OpRegexp),
+ expectedSr: andSelector{
+ lhs: mustSPName("go_memstats_*"),
+ rhs: mustRegexp("label", "valu.+"),
+ },
+ },
+ "neg regexp op: metric name with labels": {
+ input: fmt.Sprintf(`go_memstats_*{label%s"valu.+"}`, OpNegRegexp),
+ expectedSr: andSelector{
+ lhs: mustSPName("go_memstats_*"),
+ rhs: Not(mustRegexp("label", "valu.+")),
+ },
+ },
+ "sp op: metric name with labels": {
+ input: fmt.Sprintf(`go_memstats_*{label%s"valu*"}`, OpSimplePatterns),
+ expectedSr: andSelector{
+ lhs: mustSPName("go_memstats_*"),
+ rhs: mustSP("label", "valu*"),
+ },
+ },
+ "neg sp op: metric name with labels": {
+ input: fmt.Sprintf(`go_memstats_*{label%s"valu*"}`, OpNegSimplePatterns),
+ expectedSr: andSelector{
+ lhs: mustSPName("go_memstats_*"),
+ rhs: Not(mustSP("label", "valu*")),
+ },
+ },
+ "metric name with several labels": {
+ input: fmt.Sprintf(`go_memstats_*{label1%s"value1",label2%s"value2"}`, OpEqual, OpEqual),
+ expectedSr: andSelector{
+ lhs: andSelector{
+ lhs: mustSPName("go_memstats_*"),
+ rhs: mustString("label1", "value1"),
+ },
+ rhs: mustString("label2", "value2"),
+ },
+ },
+ "only labels (unsugar)": {
+ input: fmt.Sprintf(`{__name__%s"go_memstats_*",label1%s"value1",label2%s"value2"}`,
+ OpSimplePatterns, OpEqual, OpEqual),
+ expectedSr: andSelector{
+ lhs: andSelector{
+ lhs: mustSPName("go_memstats_*"),
+ rhs: mustString("label1", "value1"),
+ },
+ rhs: mustString("label2", "value2"),
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ sr, err := Parse(test.input)
+
+ if test.expectedErr {
+ assert.Error(t, err)
+ } else {
+ assert.Equal(t, test.expectedSr, sr)
+ }
+ })
+ }
+}
+
+func mustSPName(pattern string) Selector {
+ return mustSP(labels.MetricName, pattern)
+}
+
+func mustString(name string, pattern string) Selector {
+ return labelSelector{name: name, m: matcher.Must(matcher.NewStringMatcher(pattern, true, true))}
+}
+
+func mustRegexp(name string, pattern string) Selector {
+ return labelSelector{name: name, m: matcher.Must(matcher.NewRegExpMatcher(pattern))}
+}
+
+func mustSP(name string, pattern string) Selector {
+ return labelSelector{name: name, m: matcher.Must(matcher.NewSimplePatternsMatcher(pattern))}
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/selector/selector.go b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/selector.go
new file mode 100644
index 000000000..28203fca1
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/selector.go
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package selector
+
+import (
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/matcher"
+
+ "github.com/prometheus/prometheus/model/labels"
+)
+
+type Selector interface {
+ Matches(lbs labels.Labels) bool
+}
+
+const (
+ OpEqual = "="
+ OpNegEqual = "!="
+ OpRegexp = "=~"
+ OpNegRegexp = "!~"
+ OpSimplePatterns = "=*"
+ OpNegSimplePatterns = "!*"
+)
+
+type labelSelector struct {
+ name string
+ m matcher.Matcher
+}
+
+func (s labelSelector) Matches(lbs labels.Labels) bool {
+ if s.name == labels.MetricName {
+ return s.m.MatchString(lbs[0].Value)
+ }
+ if label, ok := lookupLabel(s.name, lbs[1:]); ok {
+ return s.m.MatchString(label.Value)
+ }
+ return false
+}
+
+type Func func(lbs labels.Labels) bool
+
+func (fn Func) Matches(lbs labels.Labels) bool {
+ return fn(lbs)
+}
+
+func lookupLabel(name string, lbs labels.Labels) (labels.Label, bool) {
+ for _, label := range lbs {
+ if label.Name == name {
+ return label, true
+ }
+ }
+ return labels.Label{}, false
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/selector/selector_test.go b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/selector_test.go
new file mode 100644
index 000000000..aa3110b03
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/selector/selector_test.go
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package selector
+
+import (
+ "testing"
+)
+
+func TestLabelMatcher_Matches(t *testing.T) {
+
+}
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/counter-meta.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/counter-meta.txt
new file mode 100644
index 000000000..53eccda63
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/counter-meta.txt
@@ -0,0 +1,11 @@
+# HELP test_counter_metric_1_total Test Counter Metric 1
+# TYPE test_counter_metric_1_total counter
+test_counter_metric_1_total{label1="value1"} 11
+test_counter_metric_1_total{label1="value2"} 12
+test_counter_metric_1_total{label1="value3"} 13
+test_counter_metric_1_total{label1="value4"} 14
+# TYPE test_counter_metric_2_total counter
+test_counter_metric_2_total{label1="value1"} 11
+test_counter_metric_2_total{label1="value2"} 12
+test_counter_metric_2_total{label1="value3"} 13
+test_counter_metric_2_total{label1="value4"} 14
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/counter-no-meta.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/counter-no-meta.txt
new file mode 100644
index 000000000..afb11b9b8
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/counter-no-meta.txt
@@ -0,0 +1,8 @@
+test_counter_no_meta_metric_1_total{label1="value1"} 11
+test_counter_no_meta_metric_1_total{label1="value2"} 12
+test_counter_no_meta_metric_1_total{label1="value3"} 13
+test_counter_no_meta_metric_1_total{label1="value4"} 14
+test_counter_no_meta_metric_2_total{label1="value1"} 11
+test_counter_no_meta_metric_2_total{label1="value2"} 12
+test_counter_no_meta_metric_2_total{label1="value3"} 13
+test_counter_no_meta_metric_2_total{label1="value4"} 14
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/gauge-meta.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/gauge-meta.txt
new file mode 100644
index 000000000..c0773a426
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/gauge-meta.txt
@@ -0,0 +1,11 @@
+# HELP test_gauge_metric_1 Test Gauge Metric 1
+# TYPE test_gauge_metric_1 gauge
+test_gauge_metric_1{label1="value1"} 11
+test_gauge_metric_1{label1="value2"} 12
+test_gauge_metric_1{label1="value3"} 13
+test_gauge_metric_1{label1="value4"} 14
+# TYPE test_gauge_metric_2 gauge
+test_gauge_metric_2{label1="value1"} 11
+test_gauge_metric_2{label1="value2"} 12
+test_gauge_metric_2{label1="value3"} 13
+test_gauge_metric_2{label1="value4"} 14
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/gauge-no-meta.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/gauge-no-meta.txt
new file mode 100644
index 000000000..e89e0e4d9
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/gauge-no-meta.txt
@@ -0,0 +1,8 @@
+test_gauge_no_meta_metric_1{label1="value1"} 11
+test_gauge_no_meta_metric_1{label1="value2"} 12
+test_gauge_no_meta_metric_1{label1="value3"} 13
+test_gauge_no_meta_metric_1{label1="value4"} 14
+test_gauge_no_meta_metric_2{label1="value1"} 11
+test_gauge_no_meta_metric_2{label1="value2"} 12
+test_gauge_no_meta_metric_2{label1="value3"} 13
+test_gauge_no_meta_metric_2{label1="value4"} 14
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/histogram-meta.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/histogram-meta.txt
new file mode 100644
index 000000000..9b4b8a965
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/histogram-meta.txt
@@ -0,0 +1,43 @@
+# HELP test_histogram_1_duration_seconds Test Histogram Metric 1
+# TYPE test_histogram_1_duration_seconds histogram
+test_histogram_1_duration_seconds_bucket{label1="value1",le="0.1"} 4
+test_histogram_1_duration_seconds_bucket{label1="value1",le="0.5"} 5
+test_histogram_1_duration_seconds_bucket{label1="value1",le="+Inf"} 6
+test_histogram_1_duration_seconds_sum{label1="value1"} 0.00147889
+test_histogram_1_duration_seconds_count{label1="value1"} 6
+test_histogram_1_duration_seconds_bucket{label1="value2",le="0.1"} 4
+test_histogram_1_duration_seconds_bucket{label1="value2",le="0.5"} 5
+test_histogram_1_duration_seconds_bucket{label1="value2",le="+Inf"} 6
+test_histogram_1_duration_seconds_sum{label1="value2"} 0.00147889
+test_histogram_1_duration_seconds_count{label1="value2"} 6
+test_histogram_1_duration_seconds_bucket{label1="value3",le="0.1"} 4
+test_histogram_1_duration_seconds_bucket{label1="value3",le="0.5"} 5
+test_histogram_1_duration_seconds_bucket{label1="value3",le="+Inf"} 6
+test_histogram_1_duration_seconds_sum{label1="value3"} 0.00147889
+test_histogram_1_duration_seconds_count{label1="value3"} 6
+test_histogram_1_duration_seconds_bucket{label1="value4",le="0.1"} 4
+test_histogram_1_duration_seconds_bucket{label1="value4",le="0.5"} 5
+test_histogram_1_duration_seconds_bucket{label1="value4",le="+Inf"} 6
+test_histogram_1_duration_seconds_sum{label1="value4"} 0.00147889
+test_histogram_1_duration_seconds_count{label1="value4"} 6
+# TYPE test_histogram_2_duration_seconds histogram
+test_histogram_2_duration_seconds_bucket{label1="value1",le="0.1"} 7
+test_histogram_2_duration_seconds_bucket{label1="value1",le="0.5"} 8
+test_histogram_2_duration_seconds_bucket{label1="value1",le="+Inf"} 9
+test_histogram_2_duration_seconds_sum{label1="value1"} 0.00247889
+test_histogram_2_duration_seconds_count{label1="value1"} 9
+test_histogram_2_duration_seconds_bucket{label1="value2",le="0.1"} 7
+test_histogram_2_duration_seconds_bucket{label1="value2",le="0.5"} 8
+test_histogram_2_duration_seconds_bucket{label1="value2",le="+Inf"} 9
+test_histogram_2_duration_seconds_sum{label1="value2"} 0.00247889
+test_histogram_2_duration_seconds_count{label1="value2"} 9
+test_histogram_2_duration_seconds_bucket{label1="value3",le="0.1"} 7
+test_histogram_2_duration_seconds_bucket{label1="value3",le="0.5"} 8
+test_histogram_2_duration_seconds_bucket{label1="value3",le="+Inf"} 9
+test_histogram_2_duration_seconds_sum{label1="value3"} 0.00247889
+test_histogram_2_duration_seconds_count{label1="value3"} 9
+test_histogram_2_duration_seconds_bucket{label1="value4",le="0.1"} 7
+test_histogram_2_duration_seconds_bucket{label1="value4",le="0.5"} 8
+test_histogram_2_duration_seconds_bucket{label1="value4",le="+Inf"} 9
+test_histogram_2_duration_seconds_sum{label1="value4"} 0.00247889
+test_histogram_2_duration_seconds_count{label1="value4"} 9
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/histogram-no-meta.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/histogram-no-meta.txt
new file mode 100644
index 000000000..49def677c
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/histogram-no-meta.txt
@@ -0,0 +1,40 @@
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value1",le="0.1"} 4
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value1",le="0.5"} 5
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value1",le="+Inf"} 6
+test_histogram_no_meta_1_duration_seconds_sum{label1="value1"} 0.00147889
+test_histogram_no_meta_1_duration_seconds_count{label1="value1"} 6
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value2",le="0.1"} 4
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value2",le="0.5"} 5
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value2",le="+Inf"} 6
+test_histogram_no_meta_1_duration_seconds_sum{label1="value2"} 0.00147889
+test_histogram_no_meta_1_duration_seconds_count{label1="value2"} 6
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value3",le="0.1"} 4
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value3",le="0.5"} 5
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value3",le="+Inf"} 6
+test_histogram_no_meta_1_duration_seconds_sum{label1="value3"} 0.00147889
+test_histogram_no_meta_1_duration_seconds_count{label1="value3"} 6
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value4",le="0.1"} 4
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value4",le="0.5"} 5
+test_histogram_no_meta_1_duration_seconds_bucket{label1="value4",le="+Inf"} 6
+test_histogram_no_meta_1_duration_seconds_sum{label1="value4"} 0.00147889
+test_histogram_no_meta_1_duration_seconds_count{label1="value4"} 6
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value1",le="0.1"} 7
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value1",le="0.5"} 8
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value1",le="+Inf"} 9
+test_histogram_no_meta_2_duration_seconds_sum{label1="value1"} 0.00247889
+test_histogram_no_meta_2_duration_seconds_count{label1="value1"} 9
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value2",le="0.1"} 7
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value2",le="0.5"} 8
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value2",le="+Inf"} 9
+test_histogram_no_meta_2_duration_seconds_sum{label1="value2"} 0.00247889
+test_histogram_no_meta_2_duration_seconds_count{label1="value2"} 9
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value3",le="0.1"} 7
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value3",le="0.5"} 8
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value3",le="+Inf"} 9
+test_histogram_no_meta_2_duration_seconds_sum{label1="value3"} 0.00247889
+test_histogram_no_meta_2_duration_seconds_count{label1="value3"} 9
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value4",le="0.1"} 7
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value4",le="0.5"} 8
+test_histogram_no_meta_2_duration_seconds_bucket{label1="value4",le="+Inf"} 9
+test_histogram_no_meta_2_duration_seconds_sum{label1="value4"} 0.00247889
+test_histogram_no_meta_2_duration_seconds_count{label1="value4"} 9
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/multiline-help.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/multiline-help.txt
new file mode 100644
index 000000000..f1598fcce
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/multiline-help.txt
@@ -0,0 +1,3 @@
+# HELP test_gauge_metric_1 \n First line.\n Second line.\n
+# TYPE test_gauge_metric_1 gauge
+test_gauge_metric_1{label1="value1"} 11
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/summary-meta.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/summary-meta.txt
new file mode 100644
index 000000000..3056e8076
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/summary-meta.txt
@@ -0,0 +1,43 @@
+# HELP test_summary_1_duration_microseconds Test Summary Metric 1
+# TYPE test_summary_1_duration_microseconds summary
+test_summary_1_duration_microseconds{label1="value1",quantile="0.5"} 4931.921
+test_summary_1_duration_microseconds{label1="value1",quantile="0.9"} 4932.921
+test_summary_1_duration_microseconds{label1="value1",quantile="0.99"} 4933.921
+test_summary_1_duration_microseconds_sum{label1="value1"} 283201.29
+test_summary_1_duration_microseconds_count{label1="value1"} 31
+test_summary_1_duration_microseconds{label1="value2",quantile="0.5"} 4931.921
+test_summary_1_duration_microseconds{label1="value2",quantile="0.9"} 4932.921
+test_summary_1_duration_microseconds{label1="value2",quantile="0.99"} 4933.921
+test_summary_1_duration_microseconds_sum{label1="value2"} 283201.29
+test_summary_1_duration_microseconds_count{label1="value2"} 31
+test_summary_1_duration_microseconds{label1="value3",quantile="0.5"} 4931.921
+test_summary_1_duration_microseconds{label1="value3",quantile="0.9"} 4932.921
+test_summary_1_duration_microseconds{label1="value3",quantile="0.99"} 4933.921
+test_summary_1_duration_microseconds_sum{label1="value3"} 283201.29
+test_summary_1_duration_microseconds_count{label1="value3"} 31
+test_summary_1_duration_microseconds{label1="value4",quantile="0.5"} 4931.921
+test_summary_1_duration_microseconds{label1="value4",quantile="0.9"} 4932.921
+test_summary_1_duration_microseconds{label1="value4",quantile="0.99"} 4933.921
+test_summary_1_duration_microseconds_sum{label1="value4"} 283201.29
+test_summary_1_duration_microseconds_count{label1="value4"} 31
+# TYPE test_summary_2_duration_microseconds summary
+test_summary_2_duration_microseconds{label1="value1",quantile="0.5"} 5931.921
+test_summary_2_duration_microseconds{label1="value1",quantile="0.9"} 5932.921
+test_summary_2_duration_microseconds{label1="value1",quantile="0.99"} 5933.921
+test_summary_2_duration_microseconds_sum{label1="value1"} 383201.29
+test_summary_2_duration_microseconds_count{label1="value1"} 41
+test_summary_2_duration_microseconds{label1="value2",quantile="0.5"} 5931.921
+test_summary_2_duration_microseconds{label1="value2",quantile="0.9"} 5932.921
+test_summary_2_duration_microseconds{label1="value2",quantile="0.99"} 5933.921
+test_summary_2_duration_microseconds_sum{label1="value2"} 383201.29
+test_summary_2_duration_microseconds_count{label1="value2"} 41
+test_summary_2_duration_microseconds{label1="value3",quantile="0.5"} 5931.921
+test_summary_2_duration_microseconds{label1="value3",quantile="0.9"} 5932.921
+test_summary_2_duration_microseconds{label1="value3",quantile="0.99"} 5933.921
+test_summary_2_duration_microseconds_sum{label1="value3"} 383201.29
+test_summary_2_duration_microseconds_count{label1="value3"} 41
+test_summary_2_duration_microseconds{label1="value4",quantile="0.5"} 5931.921
+test_summary_2_duration_microseconds{label1="value4",quantile="0.9"} 5932.921
+test_summary_2_duration_microseconds{label1="value4",quantile="0.99"} 5933.921
+test_summary_2_duration_microseconds_sum{label1="value4"} 383201.29
+test_summary_2_duration_microseconds_count{label1="value4"} 41
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/summary-no-meta.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/summary-no-meta.txt
new file mode 100644
index 000000000..e66564bb7
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/summary-no-meta.txt
@@ -0,0 +1,40 @@
+test_summary_no_meta_1_duration_microseconds{label1="value1",quantile="0.5"} 4931.921
+test_summary_no_meta_1_duration_microseconds{label1="value1",quantile="0.9"} 4932.921
+test_summary_no_meta_1_duration_microseconds{label1="value1",quantile="0.99"} 4933.921
+test_summary_no_meta_1_duration_microseconds_sum{label1="value1"} 283201.29
+test_summary_no_meta_1_duration_microseconds_count{label1="value1"} 31
+test_summary_no_meta_1_duration_microseconds{label1="value2",quantile="0.5"} 4931.921
+test_summary_no_meta_1_duration_microseconds{label1="value2",quantile="0.9"} 4932.921
+test_summary_no_meta_1_duration_microseconds{label1="value2",quantile="0.99"} 4933.921
+test_summary_no_meta_1_duration_microseconds_sum{label1="value2"} 283201.29
+test_summary_no_meta_1_duration_microseconds_count{label1="value2"} 31
+test_summary_no_meta_1_duration_microseconds{label1="value3",quantile="0.5"} 4931.921
+test_summary_no_meta_1_duration_microseconds{label1="value3",quantile="0.9"} 4932.921
+test_summary_no_meta_1_duration_microseconds{label1="value3",quantile="0.99"} 4933.921
+test_summary_no_meta_1_duration_microseconds_sum{label1="value3"} 283201.29
+test_summary_no_meta_1_duration_microseconds_count{label1="value3"} 31
+test_summary_no_meta_1_duration_microseconds{label1="value4",quantile="0.5"} 4931.921
+test_summary_no_meta_1_duration_microseconds{label1="value4",quantile="0.9"} 4932.921
+test_summary_no_meta_1_duration_microseconds{label1="value4",quantile="0.99"} 4933.921
+test_summary_no_meta_1_duration_microseconds_sum{label1="value4"} 283201.29
+test_summary_no_meta_1_duration_microseconds_count{label1="value4"} 31
+test_summary_no_meta_2_duration_microseconds{label1="value1",quantile="0.5"} 5931.921
+test_summary_no_meta_2_duration_microseconds{label1="value1",quantile="0.9"} 5932.921
+test_summary_no_meta_2_duration_microseconds{label1="value1",quantile="0.99"} 5933.921
+test_summary_no_meta_2_duration_microseconds_sum{label1="value1"} 383201.29
+test_summary_no_meta_2_duration_microseconds_count{label1="value1"} 41
+test_summary_no_meta_2_duration_microseconds{label1="value2",quantile="0.5"} 5931.921
+test_summary_no_meta_2_duration_microseconds{label1="value2",quantile="0.9"} 5932.921
+test_summary_no_meta_2_duration_microseconds{label1="value2",quantile="0.99"} 5933.921
+test_summary_no_meta_2_duration_microseconds_sum{label1="value2"} 383201.29
+test_summary_no_meta_2_duration_microseconds_count{label1="value2"} 41
+test_summary_no_meta_2_duration_microseconds{label1="value3",quantile="0.5"} 5931.921
+test_summary_no_meta_2_duration_microseconds{label1="value3",quantile="0.9"} 5932.921
+test_summary_no_meta_2_duration_microseconds{label1="value3",quantile="0.99"} 5933.921
+test_summary_no_meta_2_duration_microseconds_sum{label1="value3"} 383201.29
+test_summary_no_meta_2_duration_microseconds_count{label1="value3"} 41
+test_summary_no_meta_2_duration_microseconds{label1="value4",quantile="0.5"} 5931.921
+test_summary_no_meta_2_duration_microseconds{label1="value4",quantile="0.9"} 5932.921
+test_summary_no_meta_2_duration_microseconds{label1="value4",quantile="0.99"} 5933.921
+test_summary_no_meta_2_duration_microseconds_sum{label1="value4"} 383201.29
+test_summary_no_meta_2_duration_microseconds_count{label1="value4"} 41
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/testdata.nometa.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/testdata.nometa.txt
new file mode 100644
index 000000000..e760ad268
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/testdata.nometa.txt
@@ -0,0 +1,410 @@
+go_gc_duration_seconds{quantile="0"} 4.9351e-05
+go_gc_duration_seconds{quantile="0.25"} 7.424100000000001e-05
+go_gc_duration_seconds{quantile="0.5"} 8.3835e-05
+go_gc_duration_seconds{quantile="0.75"} 0.000106744
+go_gc_duration_seconds{quantile="1"} 0.002072195
+go_gc_duration_seconds_sum 0.012139815
+go_gc_duration_seconds_count 99
+go_goroutines 33
+go_memstats_alloc_bytes 1.7518624e+07
+go_memstats_alloc_bytes_total 8.3062296e+08
+go_memstats_buck_hash_sys_bytes 1.494637e+06
+go_memstats_frees_total 4.65658e+06
+go_memstats_gc_sys_bytes 1.107968e+06
+go_memstats_heap_alloc_bytes 1.7518624e+07
+go_memstats_heap_idle_bytes 6.668288e+06
+go_memstats_heap_inuse_bytes 1.8956288e+07
+go_memstats_heap_objects 72755
+go_memstats_heap_released_bytes_total 0
+go_memstats_heap_sys_bytes 2.5624576e+07
+go_memstats_last_gc_time_seconds 1.4843955586166437e+09
+go_memstats_lookups_total 2089
+go_memstats_mallocs_total 4.729335e+06
+go_memstats_mcache_inuse_bytes 9600
+go_memstats_mcache_sys_bytes 16384
+go_memstats_mspan_inuse_bytes 211520
+go_memstats_mspan_sys_bytes 245760
+go_memstats_next_gc_bytes 2.033527e+07
+go_memstats_other_sys_bytes 2.077323e+06
+go_memstats_stack_inuse_bytes 1.6384e+06
+go_memstats_stack_sys_bytes 1.6384e+06
+go_memstats_sys_bytes 3.2205048e+07
+http_request_duration_microseconds{handler="alerts",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="alerts",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="alerts",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="alerts"} 0
+http_request_duration_microseconds_count{handler="alerts"} 0
+http_request_duration_microseconds{handler="config",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="config",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="config",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="config"} 0
+http_request_duration_microseconds_count{handler="config"} 0
+http_request_duration_microseconds{handler="consoles",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="consoles",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="consoles",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="consoles"} 0
+http_request_duration_microseconds_count{handler="consoles"} 0
+http_request_duration_microseconds{handler="drop_series",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="drop_series",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="drop_series",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="drop_series"} 0
+http_request_duration_microseconds_count{handler="drop_series"} 0
+http_request_duration_microseconds{handler="federate",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="federate",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="federate",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="federate"} 0
+http_request_duration_microseconds_count{handler="federate"} 0
+http_request_duration_microseconds{handler="flags",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="flags",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="flags",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="flags"} 0
+http_request_duration_microseconds_count{handler="flags"} 0
+http_request_duration_microseconds{handler="graph",quantile="0.5"} 771.655
+http_request_duration_microseconds{handler="graph",quantile="0.9"} 1761.823
+http_request_duration_microseconds{handler="graph",quantile="0.99"} 1761.823
+http_request_duration_microseconds_sum{handler="graph"} 5803.93
+http_request_duration_microseconds_count{handler="graph"} 3
+http_request_duration_microseconds{handler="heap",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="heap",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="heap",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="heap"} 0
+http_request_duration_microseconds_count{handler="heap"} 0
+http_request_duration_microseconds{handler="label_values",quantile="0.5"} 325.401
+http_request_duration_microseconds{handler="label_values",quantile="0.9"} 414.708
+http_request_duration_microseconds{handler="label_values",quantile="0.99"} 414.708
+http_request_duration_microseconds_sum{handler="label_values"} 3995.574
+http_request_duration_microseconds_count{handler="label_values"} 3
+http_request_duration_microseconds{handler="options",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="options",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="options",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="options"} 0
+http_request_duration_microseconds_count{handler="options"} 0
+http_request_duration_microseconds{handler="prometheus",quantile="0.5"} 1351.859
+http_request_duration_microseconds{handler="prometheus",quantile="0.9"} 1714.035
+http_request_duration_microseconds{handler="prometheus",quantile="0.99"} 2833.523
+http_request_duration_microseconds_sum{handler="prometheus"} 661851.54
+http_request_duration_microseconds_count{handler="prometheus"} 462
+http_request_duration_microseconds{handler="query",quantile="0.5"} 3885.448
+http_request_duration_microseconds{handler="query",quantile="0.9"} 4390.558
+http_request_duration_microseconds{handler="query",quantile="0.99"} 4390.558
+http_request_duration_microseconds_sum{handler="query"} 26074.11
+http_request_duration_microseconds_count{handler="query"} 6
+http_request_duration_microseconds{handler="query_range",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="query_range",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="query_range",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="query_range"} 0
+http_request_duration_microseconds_count{handler="query_range"} 0
+http_request_duration_microseconds{handler="rules",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="rules",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="rules",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="rules"} 0
+http_request_duration_microseconds_count{handler="rules"} 0
+http_request_duration_microseconds{handler="series",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="series",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="series",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="series"} 0
+http_request_duration_microseconds_count{handler="series"} 0
+http_request_duration_microseconds{handler="static",quantile="0.5"} 212.311
+http_request_duration_microseconds{handler="static",quantile="0.9"} 265.174
+http_request_duration_microseconds{handler="static",quantile="0.99"} 265.174
+http_request_duration_microseconds_sum{handler="static"} 6458.621
+http_request_duration_microseconds_count{handler="static"} 3
+http_request_duration_microseconds{handler="status",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="status",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="status",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="status"} 0
+http_request_duration_microseconds_count{handler="status"} 0
+http_request_duration_microseconds{handler="targets",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="targets",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="targets",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="targets"} 0
+http_request_duration_microseconds_count{handler="targets"} 0
+http_request_duration_microseconds{handler="version",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="version",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="version",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="version"} 0
+http_request_duration_microseconds_count{handler="version"} 0
+http_request_size_bytes{handler="alerts",quantile="0.5"} NaN
+http_request_size_bytes{handler="alerts",quantile="0.9"} NaN
+http_request_size_bytes{handler="alerts",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="alerts"} 0
+http_request_size_bytes_count{handler="alerts"} 0
+http_request_size_bytes{handler="config",quantile="0.5"} NaN
+http_request_size_bytes{handler="config",quantile="0.9"} NaN
+http_request_size_bytes{handler="config",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="config"} 0
+http_request_size_bytes_count{handler="config"} 0
+http_request_size_bytes{handler="consoles",quantile="0.5"} NaN
+http_request_size_bytes{handler="consoles",quantile="0.9"} NaN
+http_request_size_bytes{handler="consoles",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="consoles"} 0
+http_request_size_bytes_count{handler="consoles"} 0
+http_request_size_bytes{handler="drop_series",quantile="0.5"} NaN
+http_request_size_bytes{handler="drop_series",quantile="0.9"} NaN
+http_request_size_bytes{handler="drop_series",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="drop_series"} 0
+http_request_size_bytes_count{handler="drop_series"} 0
+http_request_size_bytes{handler="federate",quantile="0.5"} NaN
+http_request_size_bytes{handler="federate",quantile="0.9"} NaN
+http_request_size_bytes{handler="federate",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="federate"} 0
+http_request_size_bytes_count{handler="federate"} 0
+http_request_size_bytes{handler="flags",quantile="0.5"} NaN
+http_request_size_bytes{handler="flags",quantile="0.9"} NaN
+http_request_size_bytes{handler="flags",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="flags"} 0
+http_request_size_bytes_count{handler="flags"} 0
+http_request_size_bytes{handler="graph",quantile="0.5"} 367
+http_request_size_bytes{handler="graph",quantile="0.9"} 389
+http_request_size_bytes{handler="graph",quantile="0.99"} 389
+http_request_size_bytes_sum{handler="graph"} 1145
+http_request_size_bytes_count{handler="graph"} 3
+http_request_size_bytes{handler="heap",quantile="0.5"} NaN
+http_request_size_bytes{handler="heap",quantile="0.9"} NaN
+http_request_size_bytes{handler="heap",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="heap"} 0
+http_request_size_bytes_count{handler="heap"} 0
+http_request_size_bytes{handler="label_values",quantile="0.5"} 416
+http_request_size_bytes{handler="label_values",quantile="0.9"} 416
+http_request_size_bytes{handler="label_values",quantile="0.99"} 416
+http_request_size_bytes_sum{handler="label_values"} 1248
+http_request_size_bytes_count{handler="label_values"} 3
+http_request_size_bytes{handler="options",quantile="0.5"} NaN
+http_request_size_bytes{handler="options",quantile="0.9"} NaN
+http_request_size_bytes{handler="options",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="options"} 0
+http_request_size_bytes_count{handler="options"} 0
+http_request_size_bytes{handler="prometheus",quantile="0.5"} 238
+http_request_size_bytes{handler="prometheus",quantile="0.9"} 238
+http_request_size_bytes{handler="prometheus",quantile="0.99"} 238
+http_request_size_bytes_sum{handler="prometheus"} 109956
+http_request_size_bytes_count{handler="prometheus"} 462
+http_request_size_bytes{handler="query",quantile="0.5"} 531
+http_request_size_bytes{handler="query",quantile="0.9"} 531
+http_request_size_bytes{handler="query",quantile="0.99"} 531
+http_request_size_bytes_sum{handler="query"} 3186
+http_request_size_bytes_count{handler="query"} 6
+http_request_size_bytes{handler="query_range",quantile="0.5"} NaN
+http_request_size_bytes{handler="query_range",quantile="0.9"} NaN
+http_request_size_bytes{handler="query_range",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="query_range"} 0
+http_request_size_bytes_count{handler="query_range"} 0
+http_request_size_bytes{handler="rules",quantile="0.5"} NaN
+http_request_size_bytes{handler="rules",quantile="0.9"} NaN
+http_request_size_bytes{handler="rules",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="rules"} 0
+http_request_size_bytes_count{handler="rules"} 0
+http_request_size_bytes{handler="series",quantile="0.5"} NaN
+http_request_size_bytes{handler="series",quantile="0.9"} NaN
+http_request_size_bytes{handler="series",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="series"} 0
+http_request_size_bytes_count{handler="series"} 0
+http_request_size_bytes{handler="static",quantile="0.5"} 379
+http_request_size_bytes{handler="static",quantile="0.9"} 379
+http_request_size_bytes{handler="static",quantile="0.99"} 379
+http_request_size_bytes_sum{handler="static"} 1137
+http_request_size_bytes_count{handler="static"} 3
+http_request_size_bytes{handler="status",quantile="0.5"} NaN
+http_request_size_bytes{handler="status",quantile="0.9"} NaN
+http_request_size_bytes{handler="status",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="status"} 0
+http_request_size_bytes_count{handler="status"} 0
+http_request_size_bytes{handler="targets",quantile="0.5"} NaN
+http_request_size_bytes{handler="targets",quantile="0.9"} NaN
+http_request_size_bytes{handler="targets",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="targets"} 0
+http_request_size_bytes_count{handler="targets"} 0
+http_request_size_bytes{handler="version",quantile="0.5"} NaN
+http_request_size_bytes{handler="version",quantile="0.9"} NaN
+http_request_size_bytes{handler="version",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="version"} 0
+http_request_size_bytes_count{handler="version"} 0
+http_requests_total{code="200",handler="graph",method="get"} 3
+http_requests_total{code="200",handler="label_values",method="get"} 3
+http_requests_total{code="200",handler="prometheus",method="get"} 462
+http_requests_total{code="200",handler="query",method="get"} 6
+http_requests_total{code="200",handler="static",method="get"} 3
+http_response_size_bytes{handler="alerts",quantile="0.5"} NaN
+http_response_size_bytes{handler="alerts",quantile="0.9"} NaN
+http_response_size_bytes{handler="alerts",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="alerts"} 0
+http_response_size_bytes_count{handler="alerts"} 0
+http_response_size_bytes{handler="config",quantile="0.5"} NaN
+http_response_size_bytes{handler="config",quantile="0.9"} NaN
+http_response_size_bytes{handler="config",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="config"} 0
+http_response_size_bytes_count{handler="config"} 0
+http_response_size_bytes{handler="consoles",quantile="0.5"} NaN
+http_response_size_bytes{handler="consoles",quantile="0.9"} NaN
+http_response_size_bytes{handler="consoles",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="consoles"} 0
+http_response_size_bytes_count{handler="consoles"} 0
+http_response_size_bytes{handler="drop_series",quantile="0.5"} NaN
+http_response_size_bytes{handler="drop_series",quantile="0.9"} NaN
+http_response_size_bytes{handler="drop_series",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="drop_series"} 0
+http_response_size_bytes_count{handler="drop_series"} 0
+http_response_size_bytes{handler="federate",quantile="0.5"} NaN
+http_response_size_bytes{handler="federate",quantile="0.9"} NaN
+http_response_size_bytes{handler="federate",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="federate"} 0
+http_response_size_bytes_count{handler="federate"} 0
+http_response_size_bytes{handler="flags",quantile="0.5"} NaN
+http_response_size_bytes{handler="flags",quantile="0.9"} NaN
+http_response_size_bytes{handler="flags",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="flags"} 0
+http_response_size_bytes_count{handler="flags"} 0
+http_response_size_bytes{handler="graph",quantile="0.5"} 3619
+http_response_size_bytes{handler="graph",quantile="0.9"} 3619
+http_response_size_bytes{handler="graph",quantile="0.99"} 3619
+http_response_size_bytes_sum{handler="graph"} 10857
+http_response_size_bytes_count{handler="graph"} 3
+http_response_size_bytes{handler="heap",quantile="0.5"} NaN
+http_response_size_bytes{handler="heap",quantile="0.9"} NaN
+http_response_size_bytes{handler="heap",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="heap"} 0
+http_response_size_bytes_count{handler="heap"} 0
+http_response_size_bytes{handler="label_values",quantile="0.5"} 642
+http_response_size_bytes{handler="label_values",quantile="0.9"} 642
+http_response_size_bytes{handler="label_values",quantile="0.99"} 642
+http_response_size_bytes_sum{handler="label_values"} 1926
+http_response_size_bytes_count{handler="label_values"} 3
+http_response_size_bytes{handler="options",quantile="0.5"} NaN
+http_response_size_bytes{handler="options",quantile="0.9"} NaN
+http_response_size_bytes{handler="options",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="options"} 0
+http_response_size_bytes_count{handler="options"} 0
+http_response_size_bytes{handler="prometheus",quantile="0.5"} 3033
+http_response_size_bytes{handler="prometheus",quantile="0.9"} 3123
+http_response_size_bytes{handler="prometheus",quantile="0.99"} 3128
+http_response_size_bytes_sum{handler="prometheus"} 1.374097e+06
+http_response_size_bytes_count{handler="prometheus"} 462
+http_response_size_bytes{handler="query",quantile="0.5"} 776
+http_response_size_bytes{handler="query",quantile="0.9"} 781
+http_response_size_bytes{handler="query",quantile="0.99"} 781
+http_response_size_bytes_sum{handler="query"} 4656
+http_response_size_bytes_count{handler="query"} 6
+http_response_size_bytes{handler="query_range",quantile="0.5"} NaN
+http_response_size_bytes{handler="query_range",quantile="0.9"} NaN
+http_response_size_bytes{handler="query_range",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="query_range"} 0
+http_response_size_bytes_count{handler="query_range"} 0
+http_response_size_bytes{handler="rules",quantile="0.5"} NaN
+http_response_size_bytes{handler="rules",quantile="0.9"} NaN
+http_response_size_bytes{handler="rules",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="rules"} 0
+http_response_size_bytes_count{handler="rules"} 0
+http_response_size_bytes{handler="series",quantile="0.5"} NaN
+http_response_size_bytes{handler="series",quantile="0.9"} NaN
+http_response_size_bytes{handler="series",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="series"} 0
+http_response_size_bytes_count{handler="series"} 0
+http_response_size_bytes{handler="static",quantile="0.5"} 6316
+http_response_size_bytes{handler="static",quantile="0.9"} 6316
+http_response_size_bytes{handler="static",quantile="0.99"} 6316
+http_response_size_bytes_sum{handler="static"} 18948
+http_response_size_bytes_count{handler="static"} 3
+http_response_size_bytes{handler="status",quantile="0.5"} NaN
+http_response_size_bytes{handler="status",quantile="0.9"} NaN
+http_response_size_bytes{handler="status",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="status"} 0
+http_response_size_bytes_count{handler="status"} 0
+http_response_size_bytes{handler="targets",quantile="0.5"} NaN
+http_response_size_bytes{handler="targets",quantile="0.9"} NaN
+http_response_size_bytes{handler="targets",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="targets"} 0
+http_response_size_bytes_count{handler="targets"} 0
+http_response_size_bytes{handler="version",quantile="0.5"} NaN
+http_response_size_bytes{handler="version",quantile="0.9"} NaN
+http_response_size_bytes{handler="version",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="version"} 0
+http_response_size_bytes_count{handler="version"} 0
+prometheus_build_info{branch="",goversion="go1.7.3",revision="",version=""} 1
+prometheus_config_last_reload_success_timestamp_seconds 1.484395547e+09
+prometheus_config_last_reload_successful 1
+prometheus_evaluator_duration_seconds{quantile="0.01"} 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds{quantile="0.05"} 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds{quantile="0.5"} 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds{quantile="0.9"} 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds{quantile="0.99"} 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds_sum 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds_count 1
+prometheus_evaluator_iterations_skipped_total 0
+prometheus_notifications_dropped_total 0
+prometheus_notifications_queue_capacity 10000
+prometheus_notifications_queue_length 0
+prometheus_rule_evaluation_failures_total{rule_type="alerting"} 0
+prometheus_rule_evaluation_failures_total{rule_type="recording"} 0
+prometheus_sd_azure_refresh_duration_seconds{quantile="0.5"} NaN
+prometheus_sd_azure_refresh_duration_seconds{quantile="0.9"} NaN
+prometheus_sd_azure_refresh_duration_seconds{quantile="0.99"} NaN
+prometheus_sd_azure_refresh_duration_seconds_sum 0
+prometheus_sd_azure_refresh_duration_seconds_count 0
+prometheus_sd_azure_refresh_failures_total 0
+prometheus_sd_consul_rpc_duration_seconds{call="service",endpoint="catalog",quantile="0.5"} NaN
+prometheus_sd_consul_rpc_duration_seconds{call="service",endpoint="catalog",quantile="0.9"} NaN
+prometheus_sd_consul_rpc_duration_seconds{call="service",endpoint="catalog",quantile="0.99"} NaN
+prometheus_sd_consul_rpc_duration_seconds_sum{call="service",endpoint="catalog"} 0
+prometheus_sd_consul_rpc_duration_seconds_count{call="service",endpoint="catalog"} 0
+prometheus_sd_consul_rpc_duration_seconds{call="services",endpoint="catalog",quantile="0.5"} NaN
+prometheus_sd_consul_rpc_duration_seconds{call="services",endpoint="catalog",quantile="0.9"} NaN
+prometheus_sd_consul_rpc_duration_seconds{call="services",endpoint="catalog",quantile="0.99"} NaN
+prometheus_sd_consul_rpc_duration_seconds_sum{call="services",endpoint="catalog"} 0
+prometheus_sd_consul_rpc_duration_seconds_count{call="services",endpoint="catalog"} 0
+prometheus_sd_consul_rpc_failures_total 0
+prometheus_sd_dns_lookup_failures_total 0
+prometheus_sd_dns_lookups_total 0
+prometheus_sd_ec2_refresh_duration_seconds{quantile="0.5"} NaN
+prometheus_sd_ec2_refresh_duration_seconds{quantile="0.9"} NaN
+prometheus_sd_ec2_refresh_duration_seconds{quantile="0.99"} NaN
+prometheus_sd_ec2_refresh_duration_seconds_sum 0
+prometheus_sd_ec2_refresh_duration_seconds_count 0
+prometheus_sd_ec2_refresh_failures_total 0
+prometheus_sd_file_read_errors_total 0
+prometheus_sd_file_scan_duration_seconds{quantile="0.5"} NaN
+prometheus_sd_file_scan_duration_seconds{quantile="0.9"} NaN
+prometheus_sd_file_scan_duration_seconds{quantile="0.99"} NaN
+prometheus_sd_file_scan_duration_seconds_sum 0
+prometheus_sd_file_scan_duration_seconds_count 0
+prometheus_sd_gce_refresh_duration{quantile="0.5"} NaN
+prometheus_sd_gce_refresh_duration{quantile="0.9"} NaN
+prometheus_sd_gce_refresh_duration{quantile="0.99"} NaN
+prometheus_sd_gce_refresh_duration_sum 0
+prometheus_sd_gce_refresh_duration_count 0
+prometheus_sd_gce_refresh_failures_total 0
+prometheus_sd_kubernetes_events_total{event="add",role="endpoints"} 0
+prometheus_sd_kubernetes_events_total{event="add",role="node"} 0
+prometheus_sd_kubernetes_events_total{event="add",role="pod"} 0
+prometheus_sd_kubernetes_events_total{event="add",role="service"} 0
+prometheus_sd_kubernetes_events_total{event="delete",role="endpoints"} 0
+prometheus_sd_kubernetes_events_total{event="delete",role="node"} 0
+prometheus_sd_kubernetes_events_total{event="delete",role="pod"} 0
+prometheus_sd_kubernetes_events_total{event="delete",role="service"} 0
+prometheus_sd_kubernetes_events_total{event="update",role="endpoints"} 0
+prometheus_sd_kubernetes_events_total{event="update",role="node"} 0
+prometheus_sd_kubernetes_events_total{event="update",role="pod"} 0
+prometheus_sd_kubernetes_events_total{event="update",role="service"} 0
+prometheus_sd_marathon_refresh_duration_seconds{quantile="0.5"} NaN
+prometheus_sd_marathon_refresh_duration_seconds{quantile="0.9"} NaN
+prometheus_sd_marathon_refresh_duration_seconds{quantile="0.99"} NaN
+prometheus_sd_marathon_refresh_duration_seconds_sum 0
+prometheus_sd_marathon_refresh_duration_seconds_count 0
+prometheus_sd_marathon_refresh_failures_total 0
+prometheus_target_interval_length_seconds{interval="50ms",quantile="0.01"} 0.046182157
+prometheus_target_interval_length_seconds{interval="50ms",quantile="0.05"} 0.047306979000000006
+prometheus_target_interval_length_seconds{interval="50ms",quantile="0.5"} 0.050381782
+prometheus_target_interval_length_seconds{interval="50ms",quantile="0.9"} 0.052614556
+prometheus_target_interval_length_seconds{interval="50ms",quantile="0.99"} 0.054404386000000006
+prometheus_target_interval_length_seconds_sum{interval="50ms"} 34.512091221999995
+prometheus_target_interval_length_seconds_count{interval="50ms"} 685
+prometheus_target_scrape_pool_sync_total{scrape_job="prometheus"} 1
+prometheus_target_skipped_scrapes_total 0
+prometheus_target_sync_length_seconds{scrape_job="prometheus",quantile="0.01"} 0.00020043300000000002
+prometheus_target_sync_length_seconds{scrape_job="prometheus",quantile="0.05"} 0.00020043300000000002
+prometheus_target_sync_length_seconds{scrape_job="prometheus",quantile="0.5"} 0.00020043300000000002
+prometheus_target_sync_length_seconds{scrape_job="prometheus",quantile="0.9"} 0.00020043300000000002
+prometheus_target_sync_length_seconds{scrape_job="prometheus",quantile="0.99"} 0.00020043300000000002
+prometheus_target_sync_length_seconds_sum{scrape_job="prometheus"} 0.00020043300000000002
+prometheus_target_sync_length_seconds_count{scrape_job="prometheus"} 1
+prometheus_treecache_watcher_goroutines 0
+prometheus_treecache_zookeeper_failures_total 0
diff --git a/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/testdata.txt b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/testdata.txt
new file mode 100644
index 000000000..c7f2a7af0
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/pkg/prometheus/testdata/testdata.txt
@@ -0,0 +1,528 @@
+# HELP go_gc_duration_seconds A summary of the GC invocation durations.
+# TYPE go_gc_duration_seconds summary
+go_gc_duration_seconds{quantile="0"} 4.9351e-05
+go_gc_duration_seconds{quantile="0.25"} 7.424100000000001e-05
+go_gc_duration_seconds{quantile="0.5"} 8.3835e-05
+go_gc_duration_seconds{quantile="0.75"} 0.000106744
+go_gc_duration_seconds{quantile="1"} 0.002072195
+go_gc_duration_seconds_sum 0.012139815
+go_gc_duration_seconds_count 99
+# HELP go_goroutines Number of goroutines that currently exist.
+# TYPE go_goroutines gauge
+go_goroutines 33
+# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use.
+# TYPE go_memstats_alloc_bytes gauge
+go_memstats_alloc_bytes 1.7518624e+07
+# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.
+# TYPE go_memstats_alloc_bytes_total counter
+go_memstats_alloc_bytes_total 8.3062296e+08
+# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table.
+# TYPE go_memstats_buck_hash_sys_bytes gauge
+go_memstats_buck_hash_sys_bytes 1.494637e+06
+# HELP go_memstats_frees_total Total number of frees.
+# TYPE go_memstats_frees_total counter
+go_memstats_frees_total 4.65658e+06
+# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata.
+# TYPE go_memstats_gc_sys_bytes gauge
+go_memstats_gc_sys_bytes 1.107968e+06
+# HELP go_memstats_heap_alloc_bytes Number of heap bytes allocated and still in use.
+# TYPE go_memstats_heap_alloc_bytes gauge
+go_memstats_heap_alloc_bytes 1.7518624e+07
+# HELP go_memstats_heap_idle_bytes Number of heap bytes waiting to be used.
+# TYPE go_memstats_heap_idle_bytes gauge
+go_memstats_heap_idle_bytes 6.668288e+06
+# HELP go_memstats_heap_inuse_bytes Number of heap bytes that are in use.
+# TYPE go_memstats_heap_inuse_bytes gauge
+go_memstats_heap_inuse_bytes 1.8956288e+07
+# HELP go_memstats_heap_objects Number of allocated objects.
+# TYPE go_memstats_heap_objects gauge
+go_memstats_heap_objects 72755
+# HELP go_memstats_heap_released_bytes_total Total number of heap bytes released to OS.
+# TYPE go_memstats_heap_released_bytes_total counter
+go_memstats_heap_released_bytes_total 0
+# HELP go_memstats_heap_sys_bytes Number of heap bytes obtained from system.
+# TYPE go_memstats_heap_sys_bytes gauge
+go_memstats_heap_sys_bytes 2.5624576e+07
+# HELP go_memstats_last_gc_time_seconds Number of seconds since 1970 of last garbage collection.
+# TYPE go_memstats_last_gc_time_seconds gauge
+go_memstats_last_gc_time_seconds 1.4843955586166437e+09
+# HELP go_memstats_lookups_total Total number of pointer lookups.
+# TYPE go_memstats_lookups_total counter
+go_memstats_lookups_total 2089
+# HELP go_memstats_mallocs_total Total number of mallocs.
+# TYPE go_memstats_mallocs_total counter
+go_memstats_mallocs_total 4.729335e+06
+# HELP go_memstats_mcache_inuse_bytes Number of bytes in use by mcache structures.
+# TYPE go_memstats_mcache_inuse_bytes gauge
+go_memstats_mcache_inuse_bytes 9600
+# HELP go_memstats_mcache_sys_bytes Number of bytes used for mcache structures obtained from system.
+# TYPE go_memstats_mcache_sys_bytes gauge
+go_memstats_mcache_sys_bytes 16384
+# HELP go_memstats_mspan_inuse_bytes Number of bytes in use by mspan structures.
+# TYPE go_memstats_mspan_inuse_bytes gauge
+go_memstats_mspan_inuse_bytes 211520
+# HELP go_memstats_mspan_sys_bytes Number of bytes used for mspan structures obtained from system.
+# TYPE go_memstats_mspan_sys_bytes gauge
+go_memstats_mspan_sys_bytes 245760
+# HELP go_memstats_next_gc_bytes Number of heap bytes when next garbage collection will take place.
+# TYPE go_memstats_next_gc_bytes gauge
+go_memstats_next_gc_bytes 2.033527e+07
+# HELP go_memstats_other_sys_bytes Number of bytes used for other system allocations.
+# TYPE go_memstats_other_sys_bytes gauge
+go_memstats_other_sys_bytes 2.077323e+06
+# HELP go_memstats_stack_inuse_bytes Number of bytes in use by the stack allocator.
+# TYPE go_memstats_stack_inuse_bytes gauge
+go_memstats_stack_inuse_bytes 1.6384e+06
+# HELP go_memstats_stack_sys_bytes Number of bytes obtained from system for stack allocator.
+# TYPE go_memstats_stack_sys_bytes gauge
+go_memstats_stack_sys_bytes 1.6384e+06
+# HELP go_memstats_sys_bytes Number of bytes obtained by system. Sum of all system allocations.
+# TYPE go_memstats_sys_bytes gauge
+go_memstats_sys_bytes 3.2205048e+07
+# HELP http_request_duration_microseconds The HTTP request latencies in microseconds.
+# TYPE http_request_duration_microseconds summary
+http_request_duration_microseconds{handler="alerts",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="alerts",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="alerts",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="alerts"} 0
+http_request_duration_microseconds_count{handler="alerts"} 0
+http_request_duration_microseconds{handler="config",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="config",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="config",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="config"} 0
+http_request_duration_microseconds_count{handler="config"} 0
+http_request_duration_microseconds{handler="consoles",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="consoles",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="consoles",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="consoles"} 0
+http_request_duration_microseconds_count{handler="consoles"} 0
+http_request_duration_microseconds{handler="drop_series",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="drop_series",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="drop_series",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="drop_series"} 0
+http_request_duration_microseconds_count{handler="drop_series"} 0
+http_request_duration_microseconds{handler="federate",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="federate",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="federate",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="federate"} 0
+http_request_duration_microseconds_count{handler="federate"} 0
+http_request_duration_microseconds{handler="flags",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="flags",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="flags",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="flags"} 0
+http_request_duration_microseconds_count{handler="flags"} 0
+http_request_duration_microseconds{handler="graph",quantile="0.5"} 771.655
+http_request_duration_microseconds{handler="graph",quantile="0.9"} 1761.823
+http_request_duration_microseconds{handler="graph",quantile="0.99"} 1761.823
+http_request_duration_microseconds_sum{handler="graph"} 5803.93
+http_request_duration_microseconds_count{handler="graph"} 3
+http_request_duration_microseconds{handler="heap",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="heap",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="heap",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="heap"} 0
+http_request_duration_microseconds_count{handler="heap"} 0
+http_request_duration_microseconds{handler="label_values",quantile="0.5"} 325.401
+http_request_duration_microseconds{handler="label_values",quantile="0.9"} 414.708
+http_request_duration_microseconds{handler="label_values",quantile="0.99"} 414.708
+http_request_duration_microseconds_sum{handler="label_values"} 3995.574
+http_request_duration_microseconds_count{handler="label_values"} 3
+http_request_duration_microseconds{handler="options",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="options",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="options",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="options"} 0
+http_request_duration_microseconds_count{handler="options"} 0
+http_request_duration_microseconds{handler="prometheus",quantile="0.5"} 1351.859
+http_request_duration_microseconds{handler="prometheus",quantile="0.9"} 1714.035
+http_request_duration_microseconds{handler="prometheus",quantile="0.99"} 2833.523
+http_request_duration_microseconds_sum{handler="prometheus"} 661851.54
+http_request_duration_microseconds_count{handler="prometheus"} 462
+http_request_duration_microseconds{handler="query",quantile="0.5"} 3885.448
+http_request_duration_microseconds{handler="query",quantile="0.9"} 4390.558
+http_request_duration_microseconds{handler="query",quantile="0.99"} 4390.558
+http_request_duration_microseconds_sum{handler="query"} 26074.11
+http_request_duration_microseconds_count{handler="query"} 6
+http_request_duration_microseconds{handler="query_range",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="query_range",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="query_range",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="query_range"} 0
+http_request_duration_microseconds_count{handler="query_range"} 0
+http_request_duration_microseconds{handler="rules",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="rules",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="rules",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="rules"} 0
+http_request_duration_microseconds_count{handler="rules"} 0
+http_request_duration_microseconds{handler="series",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="series",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="series",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="series"} 0
+http_request_duration_microseconds_count{handler="series"} 0
+http_request_duration_microseconds{handler="static",quantile="0.5"} 212.311
+http_request_duration_microseconds{handler="static",quantile="0.9"} 265.174
+http_request_duration_microseconds{handler="static",quantile="0.99"} 265.174
+http_request_duration_microseconds_sum{handler="static"} 6458.621
+http_request_duration_microseconds_count{handler="static"} 3
+http_request_duration_microseconds{handler="status",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="status",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="status",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="status"} 0
+http_request_duration_microseconds_count{handler="status"} 0
+http_request_duration_microseconds{handler="targets",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="targets",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="targets",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="targets"} 0
+http_request_duration_microseconds_count{handler="targets"} 0
+http_request_duration_microseconds{handler="version",quantile="0.5"} NaN
+http_request_duration_microseconds{handler="version",quantile="0.9"} NaN
+http_request_duration_microseconds{handler="version",quantile="0.99"} NaN
+http_request_duration_microseconds_sum{handler="version"} 0
+http_request_duration_microseconds_count{handler="version"} 0
+# HELP http_request_size_bytes The HTTP request sizes in bytes.
+# TYPE http_request_size_bytes summary
+http_request_size_bytes{handler="alerts",quantile="0.5"} NaN
+http_request_size_bytes{handler="alerts",quantile="0.9"} NaN
+http_request_size_bytes{handler="alerts",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="alerts"} 0
+http_request_size_bytes_count{handler="alerts"} 0
+http_request_size_bytes{handler="config",quantile="0.5"} NaN
+http_request_size_bytes{handler="config",quantile="0.9"} NaN
+http_request_size_bytes{handler="config",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="config"} 0
+http_request_size_bytes_count{handler="config"} 0
+http_request_size_bytes{handler="consoles",quantile="0.5"} NaN
+http_request_size_bytes{handler="consoles",quantile="0.9"} NaN
+http_request_size_bytes{handler="consoles",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="consoles"} 0
+http_request_size_bytes_count{handler="consoles"} 0
+http_request_size_bytes{handler="drop_series",quantile="0.5"} NaN
+http_request_size_bytes{handler="drop_series",quantile="0.9"} NaN
+http_request_size_bytes{handler="drop_series",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="drop_series"} 0
+http_request_size_bytes_count{handler="drop_series"} 0
+http_request_size_bytes{handler="federate",quantile="0.5"} NaN
+http_request_size_bytes{handler="federate",quantile="0.9"} NaN
+http_request_size_bytes{handler="federate",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="federate"} 0
+http_request_size_bytes_count{handler="federate"} 0
+http_request_size_bytes{handler="flags",quantile="0.5"} NaN
+http_request_size_bytes{handler="flags",quantile="0.9"} NaN
+http_request_size_bytes{handler="flags",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="flags"} 0
+http_request_size_bytes_count{handler="flags"} 0
+http_request_size_bytes{handler="graph",quantile="0.5"} 367
+http_request_size_bytes{handler="graph",quantile="0.9"} 389
+http_request_size_bytes{handler="graph",quantile="0.99"} 389
+http_request_size_bytes_sum{handler="graph"} 1145
+http_request_size_bytes_count{handler="graph"} 3
+http_request_size_bytes{handler="heap",quantile="0.5"} NaN
+http_request_size_bytes{handler="heap",quantile="0.9"} NaN
+http_request_size_bytes{handler="heap",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="heap"} 0
+http_request_size_bytes_count{handler="heap"} 0
+http_request_size_bytes{handler="label_values",quantile="0.5"} 416
+http_request_size_bytes{handler="label_values",quantile="0.9"} 416
+http_request_size_bytes{handler="label_values",quantile="0.99"} 416
+http_request_size_bytes_sum{handler="label_values"} 1248
+http_request_size_bytes_count{handler="label_values"} 3
+http_request_size_bytes{handler="options",quantile="0.5"} NaN
+http_request_size_bytes{handler="options",quantile="0.9"} NaN
+http_request_size_bytes{handler="options",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="options"} 0
+http_request_size_bytes_count{handler="options"} 0
+http_request_size_bytes{handler="prometheus",quantile="0.5"} 238
+http_request_size_bytes{handler="prometheus",quantile="0.9"} 238
+http_request_size_bytes{handler="prometheus",quantile="0.99"} 238
+http_request_size_bytes_sum{handler="prometheus"} 109956
+http_request_size_bytes_count{handler="prometheus"} 462
+http_request_size_bytes{handler="query",quantile="0.5"} 531
+http_request_size_bytes{handler="query",quantile="0.9"} 531
+http_request_size_bytes{handler="query",quantile="0.99"} 531
+http_request_size_bytes_sum{handler="query"} 3186
+http_request_size_bytes_count{handler="query"} 6
+http_request_size_bytes{handler="query_range",quantile="0.5"} NaN
+http_request_size_bytes{handler="query_range",quantile="0.9"} NaN
+http_request_size_bytes{handler="query_range",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="query_range"} 0
+http_request_size_bytes_count{handler="query_range"} 0
+http_request_size_bytes{handler="rules",quantile="0.5"} NaN
+http_request_size_bytes{handler="rules",quantile="0.9"} NaN
+http_request_size_bytes{handler="rules",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="rules"} 0
+http_request_size_bytes_count{handler="rules"} 0
+http_request_size_bytes{handler="series",quantile="0.5"} NaN
+http_request_size_bytes{handler="series",quantile="0.9"} NaN
+http_request_size_bytes{handler="series",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="series"} 0
+http_request_size_bytes_count{handler="series"} 0
+http_request_size_bytes{handler="static",quantile="0.5"} 379
+http_request_size_bytes{handler="static",quantile="0.9"} 379
+http_request_size_bytes{handler="static",quantile="0.99"} 379
+http_request_size_bytes_sum{handler="static"} 1137
+http_request_size_bytes_count{handler="static"} 3
+http_request_size_bytes{handler="status",quantile="0.5"} NaN
+http_request_size_bytes{handler="status",quantile="0.9"} NaN
+http_request_size_bytes{handler="status",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="status"} 0
+http_request_size_bytes_count{handler="status"} 0
+http_request_size_bytes{handler="targets",quantile="0.5"} NaN
+http_request_size_bytes{handler="targets",quantile="0.9"} NaN
+http_request_size_bytes{handler="targets",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="targets"} 0
+http_request_size_bytes_count{handler="targets"} 0
+http_request_size_bytes{handler="version",quantile="0.5"} NaN
+http_request_size_bytes{handler="version",quantile="0.9"} NaN
+http_request_size_bytes{handler="version",quantile="0.99"} NaN
+http_request_size_bytes_sum{handler="version"} 0
+http_request_size_bytes_count{handler="version"} 0
+# HELP http_requests_total Total number of HTTP requests made.
+# TYPE http_requests_total counter
+http_requests_total{code="200",handler="graph",method="get"} 3
+http_requests_total{code="200",handler="label_values",method="get"} 3
+http_requests_total{code="200",handler="prometheus",method="get"} 462
+http_requests_total{code="200",handler="query",method="get"} 6
+http_requests_total{code="200",handler="static",method="get"} 3
+# HELP http_response_size_bytes The HTTP response sizes in bytes.
+# TYPE http_response_size_bytes summary
+http_response_size_bytes{handler="alerts",quantile="0.5"} NaN
+http_response_size_bytes{handler="alerts",quantile="0.9"} NaN
+http_response_size_bytes{handler="alerts",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="alerts"} 0
+http_response_size_bytes_count{handler="alerts"} 0
+http_response_size_bytes{handler="config",quantile="0.5"} NaN
+http_response_size_bytes{handler="config",quantile="0.9"} NaN
+http_response_size_bytes{handler="config",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="config"} 0
+http_response_size_bytes_count{handler="config"} 0
+http_response_size_bytes{handler="consoles",quantile="0.5"} NaN
+http_response_size_bytes{handler="consoles",quantile="0.9"} NaN
+http_response_size_bytes{handler="consoles",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="consoles"} 0
+http_response_size_bytes_count{handler="consoles"} 0
+http_response_size_bytes{handler="drop_series",quantile="0.5"} NaN
+http_response_size_bytes{handler="drop_series",quantile="0.9"} NaN
+http_response_size_bytes{handler="drop_series",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="drop_series"} 0
+http_response_size_bytes_count{handler="drop_series"} 0
+http_response_size_bytes{handler="federate",quantile="0.5"} NaN
+http_response_size_bytes{handler="federate",quantile="0.9"} NaN
+http_response_size_bytes{handler="federate",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="federate"} 0
+http_response_size_bytes_count{handler="federate"} 0
+http_response_size_bytes{handler="flags",quantile="0.5"} NaN
+http_response_size_bytes{handler="flags",quantile="0.9"} NaN
+http_response_size_bytes{handler="flags",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="flags"} 0
+http_response_size_bytes_count{handler="flags"} 0
+http_response_size_bytes{handler="graph",quantile="0.5"} 3619
+http_response_size_bytes{handler="graph",quantile="0.9"} 3619
+http_response_size_bytes{handler="graph",quantile="0.99"} 3619
+http_response_size_bytes_sum{handler="graph"} 10857
+http_response_size_bytes_count{handler="graph"} 3
+http_response_size_bytes{handler="heap",quantile="0.5"} NaN
+http_response_size_bytes{handler="heap",quantile="0.9"} NaN
+http_response_size_bytes{handler="heap",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="heap"} 0
+http_response_size_bytes_count{handler="heap"} 0
+http_response_size_bytes{handler="label_values",quantile="0.5"} 642
+http_response_size_bytes{handler="label_values",quantile="0.9"} 642
+http_response_size_bytes{handler="label_values",quantile="0.99"} 642
+http_response_size_bytes_sum{handler="label_values"} 1926
+http_response_size_bytes_count{handler="label_values"} 3
+http_response_size_bytes{handler="options",quantile="0.5"} NaN
+http_response_size_bytes{handler="options",quantile="0.9"} NaN
+http_response_size_bytes{handler="options",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="options"} 0
+http_response_size_bytes_count{handler="options"} 0
+http_response_size_bytes{handler="prometheus",quantile="0.5"} 3033
+http_response_size_bytes{handler="prometheus",quantile="0.9"} 3123
+http_response_size_bytes{handler="prometheus",quantile="0.99"} 3128
+http_response_size_bytes_sum{handler="prometheus"} 1.374097e+06
+http_response_size_bytes_count{handler="prometheus"} 462
+http_response_size_bytes{handler="query",quantile="0.5"} 776
+http_response_size_bytes{handler="query",quantile="0.9"} 781
+http_response_size_bytes{handler="query",quantile="0.99"} 781
+http_response_size_bytes_sum{handler="query"} 4656
+http_response_size_bytes_count{handler="query"} 6
+http_response_size_bytes{handler="query_range",quantile="0.5"} NaN
+http_response_size_bytes{handler="query_range",quantile="0.9"} NaN
+http_response_size_bytes{handler="query_range",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="query_range"} 0
+http_response_size_bytes_count{handler="query_range"} 0
+http_response_size_bytes{handler="rules",quantile="0.5"} NaN
+http_response_size_bytes{handler="rules",quantile="0.9"} NaN
+http_response_size_bytes{handler="rules",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="rules"} 0
+http_response_size_bytes_count{handler="rules"} 0
+http_response_size_bytes{handler="series",quantile="0.5"} NaN
+http_response_size_bytes{handler="series",quantile="0.9"} NaN
+http_response_size_bytes{handler="series",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="series"} 0
+http_response_size_bytes_count{handler="series"} 0
+http_response_size_bytes{handler="static",quantile="0.5"} 6316
+http_response_size_bytes{handler="static",quantile="0.9"} 6316
+http_response_size_bytes{handler="static",quantile="0.99"} 6316
+http_response_size_bytes_sum{handler="static"} 18948
+http_response_size_bytes_count{handler="static"} 3
+http_response_size_bytes{handler="status",quantile="0.5"} NaN
+http_response_size_bytes{handler="status",quantile="0.9"} NaN
+http_response_size_bytes{handler="status",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="status"} 0
+http_response_size_bytes_count{handler="status"} 0
+http_response_size_bytes{handler="targets",quantile="0.5"} NaN
+http_response_size_bytes{handler="targets",quantile="0.9"} NaN
+http_response_size_bytes{handler="targets",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="targets"} 0
+http_response_size_bytes_count{handler="targets"} 0
+http_response_size_bytes{handler="version",quantile="0.5"} NaN
+http_response_size_bytes{handler="version",quantile="0.9"} NaN
+http_response_size_bytes{handler="version",quantile="0.99"} NaN
+http_response_size_bytes_sum{handler="version"} 0
+http_response_size_bytes_count{handler="version"} 0
+# HELP prometheus_build_info A metric with a constant '1' value labeled by version, revision, branch, and goversion from which prometheus was built.
+# TYPE prometheus_build_info gauge
+prometheus_build_info{branch="",goversion="go1.7.3",revision="",version=""} 1
+# HELP prometheus_config_last_reload_success_timestamp_seconds Timestamp of the last successful configuration reload.
+# TYPE prometheus_config_last_reload_success_timestamp_seconds gauge
+prometheus_config_last_reload_success_timestamp_seconds 1.484395547e+09
+# HELP prometheus_config_last_reload_successful Whether the last configuration reload attempt was successful.
+# TYPE prometheus_config_last_reload_successful gauge
+prometheus_config_last_reload_successful 1
+# HELP prometheus_evaluator_duration_seconds The duration of rule group evaluations.
+# TYPE prometheus_evaluator_duration_seconds summary
+prometheus_evaluator_duration_seconds{quantile="0.01"} 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds{quantile="0.05"} 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds{quantile="0.5"} 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds{quantile="0.9"} 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds{quantile="0.99"} 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds_sum 1.7890000000000002e-06
+prometheus_evaluator_duration_seconds_count 1
+# HELP prometheus_evaluator_iterations_skipped_total The total number of rule group evaluations skipped due to throttled metric storage.
+# TYPE prometheus_evaluator_iterations_skipped_total counter
+prometheus_evaluator_iterations_skipped_total 0
+# HELP prometheus_notifications_dropped_total Total number of alerts dropped due to alert manager missing in configuration.
+# TYPE prometheus_notifications_dropped_total counter
+prometheus_notifications_dropped_total 0
+# HELP prometheus_notifications_queue_capacity The capacity of the alert notifications queue.
+# TYPE prometheus_notifications_queue_capacity gauge
+prometheus_notifications_queue_capacity 10000
+# HELP prometheus_notifications_queue_length The number of alert notifications in the queue.
+# TYPE prometheus_notifications_queue_length gauge
+prometheus_notifications_queue_length 0
+# HELP prometheus_rule_evaluation_failures_total The total number of rule evaluation failures.
+# TYPE prometheus_rule_evaluation_failures_total counter
+prometheus_rule_evaluation_failures_total{rule_type="alerting"} 0
+prometheus_rule_evaluation_failures_total{rule_type="recording"} 0
+# HELP prometheus_sd_azure_refresh_duration_seconds The duration of a Azure-SD refresh in seconds.
+# TYPE prometheus_sd_azure_refresh_duration_seconds summary
+prometheus_sd_azure_refresh_duration_seconds{quantile="0.5"} NaN
+prometheus_sd_azure_refresh_duration_seconds{quantile="0.9"} NaN
+prometheus_sd_azure_refresh_duration_seconds{quantile="0.99"} NaN
+prometheus_sd_azure_refresh_duration_seconds_sum 0
+prometheus_sd_azure_refresh_duration_seconds_count 0
+# HELP prometheus_sd_azure_refresh_failures_total Number of Azure-SD refresh failures.
+# TYPE prometheus_sd_azure_refresh_failures_total counter
+prometheus_sd_azure_refresh_failures_total 0
+# HELP prometheus_sd_consul_rpc_duration_seconds The duration of a Consul RPC call in seconds.
+# TYPE prometheus_sd_consul_rpc_duration_seconds summary
+prometheus_sd_consul_rpc_duration_seconds{call="service",endpoint="catalog",quantile="0.5"} NaN
+prometheus_sd_consul_rpc_duration_seconds{call="service",endpoint="catalog",quantile="0.9"} NaN
+prometheus_sd_consul_rpc_duration_seconds{call="service",endpoint="catalog",quantile="0.99"} NaN
+prometheus_sd_consul_rpc_duration_seconds_sum{call="service",endpoint="catalog"} 0
+prometheus_sd_consul_rpc_duration_seconds_count{call="service",endpoint="catalog"} 0
+prometheus_sd_consul_rpc_duration_seconds{call="services",endpoint="catalog",quantile="0.5"} NaN
+prometheus_sd_consul_rpc_duration_seconds{call="services",endpoint="catalog",quantile="0.9"} NaN
+prometheus_sd_consul_rpc_duration_seconds{call="services",endpoint="catalog",quantile="0.99"} NaN
+prometheus_sd_consul_rpc_duration_seconds_sum{call="services",endpoint="catalog"} 0
+prometheus_sd_consul_rpc_duration_seconds_count{call="services",endpoint="catalog"} 0
+# HELP prometheus_sd_consul_rpc_failures_total The number of Consul RPC call failures.
+# TYPE prometheus_sd_consul_rpc_failures_total counter
+prometheus_sd_consul_rpc_failures_total 0
+# HELP prometheus_sd_dns_lookup_failures_total The number of DNS-SD lookup failures.
+# TYPE prometheus_sd_dns_lookup_failures_total counter
+prometheus_sd_dns_lookup_failures_total 0
+# HELP prometheus_sd_dns_lookups_total The number of DNS-SD lookups.
+# TYPE prometheus_sd_dns_lookups_total counter
+prometheus_sd_dns_lookups_total 0
+# HELP prometheus_sd_ec2_refresh_duration_seconds The duration of a EC2-SD refresh in seconds.
+# TYPE prometheus_sd_ec2_refresh_duration_seconds summary
+prometheus_sd_ec2_refresh_duration_seconds{quantile="0.5"} NaN
+prometheus_sd_ec2_refresh_duration_seconds{quantile="0.9"} NaN
+prometheus_sd_ec2_refresh_duration_seconds{quantile="0.99"} NaN
+prometheus_sd_ec2_refresh_duration_seconds_sum 0
+prometheus_sd_ec2_refresh_duration_seconds_count 0
+# HELP prometheus_sd_ec2_refresh_failures_total The number of EC2-SD scrape failures.
+# TYPE prometheus_sd_ec2_refresh_failures_total counter
+prometheus_sd_ec2_refresh_failures_total 0
+# HELP prometheus_sd_file_read_errors_total The number of File-SD read errors.
+# TYPE prometheus_sd_file_read_errors_total counter
+prometheus_sd_file_read_errors_total 0
+# HELP prometheus_sd_file_scan_duration_seconds The duration of the File-SD scan in seconds.
+# TYPE prometheus_sd_file_scan_duration_seconds summary
+prometheus_sd_file_scan_duration_seconds{quantile="0.5"} NaN
+prometheus_sd_file_scan_duration_seconds{quantile="0.9"} NaN
+prometheus_sd_file_scan_duration_seconds{quantile="0.99"} NaN
+prometheus_sd_file_scan_duration_seconds_sum 0
+prometheus_sd_file_scan_duration_seconds_count 0
+# HELP prometheus_sd_gce_refresh_duration The duration of a GCE-SD refresh in seconds.
+# TYPE prometheus_sd_gce_refresh_duration summary
+prometheus_sd_gce_refresh_duration{quantile="0.5"} NaN
+prometheus_sd_gce_refresh_duration{quantile="0.9"} NaN
+prometheus_sd_gce_refresh_duration{quantile="0.99"} NaN
+prometheus_sd_gce_refresh_duration_sum 0
+prometheus_sd_gce_refresh_duration_count 0
+# HELP prometheus_sd_gce_refresh_failures_total The number of GCE-SD refresh failures.
+# TYPE prometheus_sd_gce_refresh_failures_total counter
+prometheus_sd_gce_refresh_failures_total 0
+# HELP prometheus_sd_kubernetes_events_total The number of Kubernetes events handled.
+# TYPE prometheus_sd_kubernetes_events_total counter
+prometheus_sd_kubernetes_events_total{event="add",role="endpoints"} 0
+prometheus_sd_kubernetes_events_total{event="add",role="node"} 0
+prometheus_sd_kubernetes_events_total{event="add",role="pod"} 0
+prometheus_sd_kubernetes_events_total{event="add",role="service"} 0
+prometheus_sd_kubernetes_events_total{event="delete",role="endpoints"} 0
+prometheus_sd_kubernetes_events_total{event="delete",role="node"} 0
+prometheus_sd_kubernetes_events_total{event="delete",role="pod"} 0
+prometheus_sd_kubernetes_events_total{event="delete",role="service"} 0
+prometheus_sd_kubernetes_events_total{event="update",role="endpoints"} 0
+prometheus_sd_kubernetes_events_total{event="update",role="node"} 0
+prometheus_sd_kubernetes_events_total{event="update",role="pod"} 0
+prometheus_sd_kubernetes_events_total{event="update",role="service"} 0
+# HELP prometheus_sd_marathon_refresh_duration_seconds The duration of a Marathon-SD refresh in seconds.
+# TYPE prometheus_sd_marathon_refresh_duration_seconds summary
+prometheus_sd_marathon_refresh_duration_seconds{quantile="0.5"} NaN
+prometheus_sd_marathon_refresh_duration_seconds{quantile="0.9"} NaN
+prometheus_sd_marathon_refresh_duration_seconds{quantile="0.99"} NaN
+prometheus_sd_marathon_refresh_duration_seconds_sum 0
+prometheus_sd_marathon_refresh_duration_seconds_count 0
+# HELP prometheus_sd_marathon_refresh_failures_total The number of Marathon-SD refresh failures.
+# TYPE prometheus_sd_marathon_refresh_failures_total counter
+prometheus_sd_marathon_refresh_failures_total 0
+# HELP prometheus_target_interval_length_seconds Actual intervals between scrapes.
+# TYPE prometheus_target_interval_length_seconds summary
+prometheus_target_interval_length_seconds{interval="50ms",quantile="0.01"} 0.046182157
+prometheus_target_interval_length_seconds{interval="50ms",quantile="0.05"} 0.047306979000000006
+prometheus_target_interval_length_seconds{interval="50ms",quantile="0.5"} 0.050381782
+prometheus_target_interval_length_seconds{interval="50ms",quantile="0.9"} 0.052614556
+prometheus_target_interval_length_seconds{interval="50ms",quantile="0.99"} 0.054404386000000006
+prometheus_target_interval_length_seconds_sum{interval="50ms"} 34.512091221999995
+prometheus_target_interval_length_seconds_count{interval="50ms"} 685
+# HELP prometheus_target_scrape_pool_sync_total Total number of syncs that were executed on a scrape pool.
+# TYPE prometheus_target_scrape_pool_sync_total counter
+prometheus_target_scrape_pool_sync_total{scrape_job="prometheus"} 1
+# HELP prometheus_target_skipped_scrapes_total Total number of scrapes that were skipped because the metric storage was throttled.
+# TYPE prometheus_target_skipped_scrapes_total counter
+prometheus_target_skipped_scrapes_total 0
+# HELP prometheus_target_sync_length_seconds Actual interval to sync the scrape pool.
+# TYPE prometheus_target_sync_length_seconds summary
+prometheus_target_sync_length_seconds{scrape_job="prometheus",quantile="0.01"} 0.00020043300000000002
+prometheus_target_sync_length_seconds{scrape_job="prometheus",quantile="0.05"} 0.00020043300000000002
+prometheus_target_sync_length_seconds{scrape_job="prometheus",quantile="0.5"} 0.00020043300000000002
+prometheus_target_sync_length_seconds{scrape_job="prometheus",quantile="0.9"} 0.00020043300000000002
+prometheus_target_sync_length_seconds{scrape_job="prometheus",quantile="0.99"} 0.00020043300000000002
+prometheus_target_sync_length_seconds_sum{scrape_job="prometheus"} 0.00020043300000000002
+prometheus_target_sync_length_seconds_count{scrape_job="prometheus"} 1
+# HELP prometheus_treecache_watcher_goroutines The current number of watcher goroutines.
+# TYPE prometheus_treecache_watcher_goroutines gauge
+prometheus_treecache_watcher_goroutines 0
+# HELP prometheus_treecache_zookeeper_failures_total The total number of ZooKeeper failures.
+# TYPE prometheus_treecache_zookeeper_failures_total counter
+prometheus_treecache_zookeeper_failures_total 0 \ No newline at end of file