...

Source file src/github.com/go-kit/kit/metrics/prometheus/prometheus_test.go

Documentation: github.com/go-kit/kit/metrics/prometheus

     1  package prometheus
     2  
     3  import (
     4  	"io/ioutil"
     5  	"math"
     6  	"math/rand"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"reflect"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/go-kit/kit/metrics/teststat"
    16  	stdprometheus "github.com/prometheus/client_golang/prometheus"
    17  	"github.com/prometheus/client_golang/prometheus/promhttp"
    18  )
    19  
    20  func TestCounter(t *testing.T) {
    21  	s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{}))
    22  	defer s.Close()
    23  
    24  	scrape := func() string {
    25  		resp, _ := http.Get(s.URL)
    26  		buf, _ := ioutil.ReadAll(resp.Body)
    27  		return string(buf)
    28  	}
    29  
    30  	namespace, subsystem, name := "ns", "ss", "foo"
    31  	re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{alpha="alpha-value",beta="beta-value"} ([0-9\.]+)`)
    32  
    33  	counter := NewCounterFrom(stdprometheus.CounterOpts{
    34  		Namespace: namespace,
    35  		Subsystem: subsystem,
    36  		Name:      name,
    37  		Help:      "This is the help string.",
    38  	}, []string{"alpha", "beta"}).With("beta", "beta-value", "alpha", "alpha-value") // order shouldn't matter
    39  
    40  	value := func() float64 {
    41  		matches := re.FindStringSubmatch(scrape())
    42  		f, _ := strconv.ParseFloat(matches[1], 64)
    43  		return f
    44  	}
    45  
    46  	if err := teststat.TestCounter(counter, value); err != nil {
    47  		t.Fatal(err)
    48  	}
    49  }
    50  
    51  func TestGauge(t *testing.T) {
    52  	s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{}))
    53  	defer s.Close()
    54  
    55  	scrape := func() string {
    56  		resp, _ := http.Get(s.URL)
    57  		buf, _ := ioutil.ReadAll(resp.Body)
    58  		return string(buf)
    59  	}
    60  
    61  	namespace, subsystem, name := "aaa", "bbb", "ccc"
    62  	re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{foo="bar"} ([0-9\.]+)`)
    63  
    64  	gauge := NewGaugeFrom(stdprometheus.GaugeOpts{
    65  		Namespace: namespace,
    66  		Subsystem: subsystem,
    67  		Name:      name,
    68  		Help:      "This is a different help string.",
    69  	}, []string{"foo"}).With("foo", "bar")
    70  
    71  	value := func() []float64 {
    72  		matches := re.FindStringSubmatch(scrape())
    73  		f, _ := strconv.ParseFloat(matches[1], 64)
    74  		return []float64{f}
    75  	}
    76  
    77  	if err := teststat.TestGauge(gauge, value); err != nil {
    78  		t.Fatal(err)
    79  	}
    80  }
    81  
    82  func TestSummary(t *testing.T) {
    83  	s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{}))
    84  	defer s.Close()
    85  
    86  	scrape := func() string {
    87  		resp, _ := http.Get(s.URL)
    88  		buf, _ := ioutil.ReadAll(resp.Body)
    89  		return string(buf)
    90  	}
    91  
    92  	namespace, subsystem, name := "test", "prometheus", "summary"
    93  	re50 := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{a="a",b="b",quantile="0.5"} ([0-9\.]+)`)
    94  	re90 := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{a="a",b="b",quantile="0.9"} ([0-9\.]+)`)
    95  	re99 := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{a="a",b="b",quantile="0.99"} ([0-9\.]+)`)
    96  
    97  	summary := NewSummaryFrom(stdprometheus.SummaryOpts{
    98  		Namespace:  namespace,
    99  		Subsystem:  subsystem,
   100  		Name:       name,
   101  		Help:       "This is the help string for the summary.",
   102  		Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
   103  	}, []string{"a", "b"}).With("b", "b").With("a", "a")
   104  
   105  	quantiles := func() (float64, float64, float64, float64) {
   106  		buf := scrape()
   107  		match50 := re50.FindStringSubmatch(buf)
   108  		p50, _ := strconv.ParseFloat(match50[1], 64)
   109  		match90 := re90.FindStringSubmatch(buf)
   110  		p90, _ := strconv.ParseFloat(match90[1], 64)
   111  		match99 := re99.FindStringSubmatch(buf)
   112  		p99, _ := strconv.ParseFloat(match99[1], 64)
   113  		p95 := p90 + ((p99 - p90) / 2) // Prometheus, y u no p95??? :< #yolo
   114  		return p50, p90, p95, p99
   115  	}
   116  
   117  	if err := teststat.TestHistogram(summary, quantiles, 0.01); err != nil {
   118  		t.Fatal(err)
   119  	}
   120  }
   121  
   122  func TestHistogram(t *testing.T) {
   123  	// Prometheus reports histograms as a count of observations that fell into
   124  	// each predefined bucket, with the bucket value representing a global upper
   125  	// limit. That is, the count monotonically increases over the buckets. This
   126  	// requires a different strategy to test.
   127  
   128  	s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{}))
   129  	defer s.Close()
   130  
   131  	scrape := func() string {
   132  		resp, _ := http.Get(s.URL)
   133  		buf, _ := ioutil.ReadAll(resp.Body)
   134  		return string(buf)
   135  	}
   136  
   137  	namespace, subsystem, name := "test", "prometheus", "histogram"
   138  	re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `_bucket{x="1",le="([0-9]+|\+Inf)"} ([0-9\.]+)`)
   139  
   140  	numStdev := 3
   141  	bucketMin := (teststat.Mean - (numStdev * teststat.Stdev))
   142  	bucketMax := (teststat.Mean + (numStdev * teststat.Stdev))
   143  	if bucketMin < 0 {
   144  		bucketMin = 0
   145  	}
   146  	bucketCount := 10
   147  	bucketDelta := (bucketMax - bucketMin) / bucketCount
   148  	buckets := []float64{}
   149  	for i := bucketMin; i <= bucketMax; i += bucketDelta {
   150  		buckets = append(buckets, float64(i))
   151  	}
   152  
   153  	histogram := NewHistogramFrom(stdprometheus.HistogramOpts{
   154  		Namespace: namespace,
   155  		Subsystem: subsystem,
   156  		Name:      name,
   157  		Help:      "This is the help string for the histogram.",
   158  		Buckets:   buckets,
   159  	}, []string{"x"}).With("x", "1")
   160  
   161  	// Can't TestHistogram, because Prometheus Histograms don't dynamically
   162  	// compute quantiles. Instead, they fill up buckets. So, let's populate the
   163  	// histogram kind of manually.
   164  	teststat.PopulateNormalHistogram(histogram, rand.Int())
   165  
   166  	// Then, we use ExpectedObservationsLessThan to validate.
   167  	for _, line := range strings.Split(scrape(), "\n") {
   168  		match := re.FindStringSubmatch(line)
   169  		if match == nil {
   170  			continue
   171  		}
   172  
   173  		bucket, _ := strconv.ParseInt(match[1], 10, 64)
   174  		have, _ := strconv.ParseFloat(match[2], 64)
   175  
   176  		want := teststat.ExpectedObservationsLessThan(bucket)
   177  		if match[1] == "+Inf" {
   178  			want = int64(teststat.Count) // special case
   179  		}
   180  
   181  		// Unfortunately, we observe experimentally that Prometheus is quite
   182  		// imprecise at the extremes. I'm setting a very high tolerance for now.
   183  		// It would be great to dig in and figure out whether that's a problem
   184  		// with my Expected calculation, or in Prometheus.
   185  		tolerance := 0.25
   186  		if delta := math.Abs(float64(want) - float64(have)); (delta / float64(want)) > tolerance {
   187  			t.Errorf("Bucket %d: want %d, have %d (%.1f%%)", bucket, want, int(have), (100.0 * delta / float64(want)))
   188  		}
   189  	}
   190  }
   191  
   192  func TestInconsistentLabelCardinality(t *testing.T) {
   193  	defer func() {
   194  		x := recover()
   195  		if x == nil {
   196  			t.Fatal("expected panic, got none")
   197  		}
   198  		err, ok := x.(error)
   199  		if !ok {
   200  			t.Fatalf("expected error, got %s", reflect.TypeOf(x))
   201  		}
   202  		if want, have := "inconsistent label cardinality", err.Error(); !strings.HasPrefix(have, want) {
   203  			t.Fatalf("want %q, have %q", want, have)
   204  		}
   205  	}()
   206  
   207  	NewCounterFrom(stdprometheus.CounterOpts{
   208  		Namespace: "test",
   209  		Subsystem: "inconsistent_label_cardinality",
   210  		Name:      "foobar",
   211  		Help:      "This is the help string for the metric.",
   212  	}, []string{"a", "b"}).With(
   213  		"a", "1", "b", "2", "c", "KABOOM!",
   214  	).Add(123)
   215  }
   216  

View as plain text