| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | // Adapted by Miek Gieben for CoreDNS testing.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // License from prom2json
 | 
					
						
							|  |  |  | // Copyright 2014 Prometheus Team
 | 
					
						
							|  |  |  | // Licensed under the Apache License, Version 2.0 (the "License");
 | 
					
						
							|  |  |  | // you may not use this file except in compliance with the License.
 | 
					
						
							|  |  |  | // You may obtain a copy of the License at
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Unless required by applicable law or agreed to in writing, software
 | 
					
						
							|  |  |  | // distributed under the License is distributed on an "AS IS" BASIS,
 | 
					
						
							|  |  |  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
					
						
							|  |  |  | // See the License for the specific language governing permissions and
 | 
					
						
							|  |  |  | // limitations under the License.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Package test will scrape a target and you can inspect the variables.
 | 
					
						
							|  |  |  | // Basic usage:
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	result := Scrape("http://localhost:9153/metrics")
 | 
					
						
							| 
									
										
										
										
											2016-10-30 10:06:57 +01:00
										 |  |  | //	v := MetricValue("coredns_cache_capacity", result)
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | //
 | 
					
						
							|  |  |  | package test
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							|  |  |  | 	"fmt"
 | 
					
						
							|  |  |  | 	"io"
 | 
					
						
							|  |  |  | 	"mime"
 | 
					
						
							|  |  |  | 	"net/http"
 | 
					
						
							| 
									
										
										
										
											2018-11-01 15:56:00 -04:00
										 |  |  | 	"strconv"
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/matttproud/golang_protobuf_extensions/pbutil"
 | 
					
						
							|  |  |  | 	dto "github.com/prometheus/client_model/go"
 | 
					
						
							| 
									
										
										
										
											2020-01-30 09:19:26 +00:00
										 |  |  | 	"github.com/prometheus/common/expfmt"
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type (
 | 
					
						
							|  |  |  | 	// MetricFamily holds a prometheus metric.
 | 
					
						
							|  |  |  | 	MetricFamily struct {
 | 
					
						
							|  |  |  | 		Name    string        `json:"name"`
 | 
					
						
							|  |  |  | 		Help    string        `json:"help"`
 | 
					
						
							|  |  |  | 		Type    string        `json:"type"`
 | 
					
						
							|  |  |  | 		Metrics []interface{} `json:"metrics,omitempty"` // Either metric or summary.
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// metric is for all "single value" metrics.
 | 
					
						
							|  |  |  | 	metric struct {
 | 
					
						
							|  |  |  | 		Labels map[string]string `json:"labels,omitempty"`
 | 
					
						
							|  |  |  | 		Value  string            `json:"value"`
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	summary struct {
 | 
					
						
							|  |  |  | 		Labels    map[string]string `json:"labels,omitempty"`
 | 
					
						
							|  |  |  | 		Quantiles map[string]string `json:"quantiles,omitempty"`
 | 
					
						
							|  |  |  | 		Count     string            `json:"count"`
 | 
					
						
							|  |  |  | 		Sum       string            `json:"sum"`
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	histogram struct {
 | 
					
						
							|  |  |  | 		Labels  map[string]string `json:"labels,omitempty"`
 | 
					
						
							|  |  |  | 		Buckets map[string]string `json:"buckets,omitempty"`
 | 
					
						
							|  |  |  | 		Count   string            `json:"count"`
 | 
					
						
							|  |  |  | 		Sum     string            `json:"sum"`
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Scrape returns the all the vars a []*metricFamily.
 | 
					
						
							| 
									
										
										
										
											2019-01-19 11:23:13 +00:00
										 |  |  | func Scrape(url string) []*MetricFamily {
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | 	mfChan := make(chan *dto.MetricFamily, 1024)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-06 05:54:24 -07:00
										 |  |  | 	go fetchMetricFamilies(url, mfChan)
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	result := []*MetricFamily{}
 | 
					
						
							|  |  |  | 	for mf := range mfChan {
 | 
					
						
							|  |  |  | 		result = append(result, newMetricFamily(mf))
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return result
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-01 15:10:45 +08:00
										 |  |  | // ScrapeMetricAsInt provides a sum of all metrics collected for the name and label provided.
 | 
					
						
							| 
									
										
										
										
											2018-11-01 15:56:00 -04:00
										 |  |  | // if the metric is not a numeric value, it will be counted a 0.
 | 
					
						
							| 
									
										
										
										
											2019-01-19 11:23:13 +00:00
										 |  |  | func ScrapeMetricAsInt(addr string, name string, label string, nometricvalue int) int {
 | 
					
						
							| 
									
										
										
										
											2018-11-01 15:56:00 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	valueToInt := func(m metric) int {
 | 
					
						
							|  |  |  | 		v := m.Value
 | 
					
						
							|  |  |  | 		r, err := strconv.Atoi(v)
 | 
					
						
							|  |  |  | 		if err != nil {
 | 
					
						
							|  |  |  | 			return 0
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		return r
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-19 11:23:13 +00:00
										 |  |  | 	met := Scrape(fmt.Sprintf("http://%s/metrics", addr))
 | 
					
						
							| 
									
										
										
										
											2018-11-01 15:56:00 -04:00
										 |  |  | 	found := false
 | 
					
						
							|  |  |  | 	tot := 0
 | 
					
						
							|  |  |  | 	for _, mf := range met {
 | 
					
						
							|  |  |  | 		if mf.Name == name {
 | 
					
						
							|  |  |  | 			// Sum all metrics available
 | 
					
						
							|  |  |  | 			for _, m := range mf.Metrics {
 | 
					
						
							|  |  |  | 				if label == "" {
 | 
					
						
							|  |  |  | 					tot += valueToInt(m.(metric))
 | 
					
						
							|  |  |  | 					found = true
 | 
					
						
							|  |  |  | 					continue
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				for _, v := range m.(metric).Labels {
 | 
					
						
							|  |  |  | 					if v == label {
 | 
					
						
							|  |  |  | 						tot += valueToInt(m.(metric))
 | 
					
						
							|  |  |  | 						found = true
 | 
					
						
							|  |  |  | 					}
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !found {
 | 
					
						
							|  |  |  | 		return nometricvalue
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return tot
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | // MetricValue returns the value associated with name as a string as well as the labels.
 | 
					
						
							|  |  |  | // It only returns the first metrics of the slice.
 | 
					
						
							|  |  |  | func MetricValue(name string, mfs []*MetricFamily) (string, map[string]string) {
 | 
					
						
							|  |  |  | 	for _, mf := range mfs {
 | 
					
						
							|  |  |  | 		if mf.Name == name {
 | 
					
						
							|  |  |  | 			// Only works with Gauge and Counter...
 | 
					
						
							|  |  |  | 			return mf.Metrics[0].(metric).Value, mf.Metrics[0].(metric).Labels
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return "", nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MetricValueLabel returns the value for name *and* label *value*.
 | 
					
						
							|  |  |  | func MetricValueLabel(name, label string, mfs []*MetricFamily) (string, map[string]string) {
 | 
					
						
							|  |  |  | 	// bit hacky is this really handy...?
 | 
					
						
							|  |  |  | 	for _, mf := range mfs {
 | 
					
						
							|  |  |  | 		if mf.Name == name {
 | 
					
						
							|  |  |  | 			for _, m := range mf.Metrics {
 | 
					
						
							|  |  |  | 				for _, v := range m.(metric).Labels {
 | 
					
						
							|  |  |  | 					if v == label {
 | 
					
						
							|  |  |  | 						return m.(metric).Value, m.(metric).Labels
 | 
					
						
							|  |  |  | 					}
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return "", nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func newMetricFamily(dtoMF *dto.MetricFamily) *MetricFamily {
 | 
					
						
							|  |  |  | 	mf := &MetricFamily{
 | 
					
						
							|  |  |  | 		Name:    dtoMF.GetName(),
 | 
					
						
							|  |  |  | 		Help:    dtoMF.GetHelp(),
 | 
					
						
							|  |  |  | 		Type:    dtoMF.GetType().String(),
 | 
					
						
							|  |  |  | 		Metrics: make([]interface{}, len(dtoMF.Metric)),
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	for i, m := range dtoMF.Metric {
 | 
					
						
							|  |  |  | 		if dtoMF.GetType() == dto.MetricType_SUMMARY {
 | 
					
						
							|  |  |  | 			mf.Metrics[i] = summary{
 | 
					
						
							|  |  |  | 				Labels:    makeLabels(m),
 | 
					
						
							|  |  |  | 				Quantiles: makeQuantiles(m),
 | 
					
						
							|  |  |  | 				Count:     fmt.Sprint(m.GetSummary().GetSampleCount()),
 | 
					
						
							|  |  |  | 				Sum:       fmt.Sprint(m.GetSummary().GetSampleSum()),
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		} else if dtoMF.GetType() == dto.MetricType_HISTOGRAM {
 | 
					
						
							|  |  |  | 			mf.Metrics[i] = histogram{
 | 
					
						
							|  |  |  | 				Labels:  makeLabels(m),
 | 
					
						
							|  |  |  | 				Buckets: makeBuckets(m),
 | 
					
						
							|  |  |  | 				Count:   fmt.Sprint(m.GetHistogram().GetSampleCount()),
 | 
					
						
							|  |  |  | 				Sum:     fmt.Sprint(m.GetSummary().GetSampleSum()),
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		} else {
 | 
					
						
							|  |  |  | 			mf.Metrics[i] = metric{
 | 
					
						
							|  |  |  | 				Labels: makeLabels(m),
 | 
					
						
							|  |  |  | 				Value:  fmt.Sprint(value(m)),
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return mf
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func value(m *dto.Metric) float64 {
 | 
					
						
							|  |  |  | 	if m.Gauge != nil {
 | 
					
						
							|  |  |  | 		return m.GetGauge().GetValue()
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	if m.Counter != nil {
 | 
					
						
							|  |  |  | 		return m.GetCounter().GetValue()
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	if m.Untyped != nil {
 | 
					
						
							|  |  |  | 		return m.GetUntyped().GetValue()
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return 0.
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func makeLabels(m *dto.Metric) map[string]string {
 | 
					
						
							|  |  |  | 	result := map[string]string{}
 | 
					
						
							|  |  |  | 	for _, lp := range m.Label {
 | 
					
						
							|  |  |  | 		result[lp.GetName()] = lp.GetValue()
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return result
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func makeQuantiles(m *dto.Metric) map[string]string {
 | 
					
						
							|  |  |  | 	result := map[string]string{}
 | 
					
						
							|  |  |  | 	for _, q := range m.GetSummary().Quantile {
 | 
					
						
							|  |  |  | 		result[fmt.Sprint(q.GetQuantile())] = fmt.Sprint(q.GetValue())
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return result
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func makeBuckets(m *dto.Metric) map[string]string {
 | 
					
						
							|  |  |  | 	result := map[string]string{}
 | 
					
						
							|  |  |  | 	for _, b := range m.GetHistogram().Bucket {
 | 
					
						
							|  |  |  | 		result[fmt.Sprint(b.GetUpperBound())] = fmt.Sprint(b.GetCumulativeCount())
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return result
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-06 05:54:24 -07:00
										 |  |  | func fetchMetricFamilies(url string, ch chan<- *dto.MetricFamily) {
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | 	defer close(ch)
 | 
					
						
							|  |  |  | 	req, err := http.NewRequest("GET", url, nil)
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							| 
									
										
										
										
											2017-08-06 05:54:24 -07:00
										 |  |  | 		return
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	req.Header.Add("Accept", acceptHeader)
 | 
					
						
							|  |  |  | 	resp, err := http.DefaultClient.Do(req)
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							| 
									
										
										
										
											2017-08-06 05:54:24 -07:00
										 |  |  | 		return
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	defer resp.Body.Close()
 | 
					
						
							|  |  |  | 	if resp.StatusCode != http.StatusOK {
 | 
					
						
							| 
									
										
										
										
											2017-08-06 05:54:24 -07:00
										 |  |  | 		return
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mediatype, params, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
 | 
					
						
							|  |  |  | 	if err == nil && mediatype == "application/vnd.google.protobuf" &&
 | 
					
						
							|  |  |  | 		params["encoding"] == "delimited" &&
 | 
					
						
							|  |  |  | 		params["proto"] == "io.prometheus.client.MetricFamily" {
 | 
					
						
							|  |  |  | 		for {
 | 
					
						
							|  |  |  | 			mf := &dto.MetricFamily{}
 | 
					
						
							|  |  |  | 			if _, err = pbutil.ReadDelimited(resp.Body, mf); err != nil {
 | 
					
						
							|  |  |  | 				if err == io.EOF {
 | 
					
						
							|  |  |  | 					break
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							| 
									
										
										
										
											2017-08-06 05:54:24 -07:00
										 |  |  | 				return
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | 			}
 | 
					
						
							|  |  |  | 			ch <- mf
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	} else {
 | 
					
						
							|  |  |  | 		// We could do further content-type checks here, but the
 | 
					
						
							|  |  |  | 		// fallback for now will anyway be the text format
 | 
					
						
							|  |  |  | 		// version 0.0.4, so just go for it and see if it works.
 | 
					
						
							|  |  |  | 		var parser expfmt.TextParser
 | 
					
						
							|  |  |  | 		metricFamilies, err := parser.TextToMetricFamilies(resp.Body)
 | 
					
						
							|  |  |  | 		if err != nil {
 | 
					
						
							| 
									
										
										
										
											2017-08-06 05:54:24 -07:00
										 |  |  | 			return
 | 
					
						
							| 
									
										
										
										
											2016-10-26 10:01:52 +01:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 		for _, mf := range metricFamilies {
 | 
					
						
							|  |  |  | 			ch <- mf
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const acceptHeader = `application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.7,text/plain;version=0.0.4;q=0.3`
 |