mirror of
https://github.com/coredns/coredns.git
synced 2025-08-07 06:47:01 +02:00
Enable protogetter in golangci config and update all protobuf field access to use getter methods instead of direct field access. Getter methods provide safer nil pointer handling and return appropriate default values, following protobuf best practices. Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
263 lines
6.9 KiB
Go
263 lines
6.9 KiB
Go
// 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")
|
|
// v := MetricValue("coredns_cache_capacity", result)
|
|
package test
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"mime"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/matttproud/golang_protobuf_extensions/pbutil"
|
|
dto "github.com/prometheus/client_model/go"
|
|
"github.com/prometheus/common/expfmt"
|
|
)
|
|
|
|
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.
|
|
func Scrape(url string) []*MetricFamily {
|
|
mfChan := make(chan *dto.MetricFamily, 1024)
|
|
|
|
go fetchMetricFamilies(url, mfChan)
|
|
|
|
result := []*MetricFamily{}
|
|
for mf := range mfChan {
|
|
result = append(result, newMetricFamily(mf))
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ScrapeMetricAsInt provides a sum of all metrics collected for the name and label provided.
|
|
// if the metric is not a numeric value, it will be counted a 0.
|
|
func ScrapeMetricAsInt(addr string, name string, label string, nometricvalue int) int {
|
|
valueToInt := func(m metric) int {
|
|
v := m.Value
|
|
r, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return r
|
|
}
|
|
|
|
met := Scrape(fmt.Sprintf("http://%s/metrics", addr))
|
|
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
|
|
}
|
|
|
|
// 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.GetMetric())),
|
|
}
|
|
for i, m := range dtoMF.GetMetric() {
|
|
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.GetGauge() != nil {
|
|
return m.GetGauge().GetValue()
|
|
}
|
|
if m.GetCounter() != nil {
|
|
return m.GetCounter().GetValue()
|
|
}
|
|
if m.GetUntyped() != nil {
|
|
return m.GetUntyped().GetValue()
|
|
}
|
|
return 0.
|
|
}
|
|
|
|
func makeLabels(m *dto.Metric) map[string]string {
|
|
result := map[string]string{}
|
|
for _, lp := range m.GetLabel() {
|
|
result[lp.GetName()] = lp.GetValue()
|
|
}
|
|
return result
|
|
}
|
|
|
|
func makeQuantiles(m *dto.Metric) map[string]string {
|
|
result := map[string]string{}
|
|
for _, q := range m.GetSummary().GetQuantile() {
|
|
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().GetBucket() {
|
|
result[fmt.Sprint(b.GetUpperBound())] = fmt.Sprint(b.GetCumulativeCount())
|
|
}
|
|
return result
|
|
}
|
|
|
|
func fetchMetricFamilies(url string, ch chan<- *dto.MetricFamily) {
|
|
defer close(ch)
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
req.Header.Add("Accept", acceptHeader)
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
return
|
|
}
|
|
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 {
|
|
return
|
|
}
|
|
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`
|