15 stdprometheus "github.com/prometheus/client_golang/prometheus"
17 "github.com/go-kit/kit/metrics/teststat"
20 func TestCounter(t *testing.T) {
21 s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
24 scrape := func() string {
25 resp, _ := http.Get(s.URL)
26 buf, _ := ioutil.ReadAll(resp.Body)
30 namespace, subsystem, name := "ns", "ss", "foo"
31 re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{alpha="alpha-value",beta="beta-value"} ([0-9\.]+)`)
33 counter := NewCounterFrom(stdprometheus.CounterOpts{
37 Help: "This is the help string.",
38 }, []string{"alpha", "beta"}).With("beta", "beta-value", "alpha", "alpha-value") // order shouldn't matter
40 value := func() float64 {
41 matches := re.FindStringSubmatch(scrape())
42 f, _ := strconv.ParseFloat(matches[1], 64)
46 if err := teststat.TestCounter(counter, value); err != nil {
51 func TestGauge(t *testing.T) {
52 s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
55 scrape := func() string {
56 resp, _ := http.Get(s.URL)
57 buf, _ := ioutil.ReadAll(resp.Body)
61 namespace, subsystem, name := "aaa", "bbb", "ccc"
62 re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{foo="bar"} ([0-9\.]+)`)
64 gauge := NewGaugeFrom(stdprometheus.GaugeOpts{
68 Help: "This is a different help string.",
69 }, []string{"foo"}).With("foo", "bar")
71 value := func() float64 {
72 matches := re.FindStringSubmatch(scrape())
73 f, _ := strconv.ParseFloat(matches[1], 64)
77 if err := teststat.TestGauge(gauge, value); err != nil {
82 func TestSummary(t *testing.T) {
83 s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
86 scrape := func() string {
87 resp, _ := http.Get(s.URL)
88 buf, _ := ioutil.ReadAll(resp.Body)
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\.]+)`)
97 summary := NewSummaryFrom(stdprometheus.SummaryOpts{
101 Help: "This is the help string for the summary.",
102 }, []string{"a", "b"}).With("b", "b").With("a", "a")
104 quantiles := func() (float64, float64, float64, float64) {
106 match50 := re50.FindStringSubmatch(buf)
107 p50, _ := strconv.ParseFloat(match50[1], 64)
108 match90 := re90.FindStringSubmatch(buf)
109 p90, _ := strconv.ParseFloat(match90[1], 64)
110 match99 := re99.FindStringSubmatch(buf)
111 p99, _ := strconv.ParseFloat(match99[1], 64)
112 p95 := p90 + ((p99 - p90) / 2) // Prometheus, y u no p95??? :< #yolo
113 return p50, p90, p95, p99
116 if err := teststat.TestHistogram(summary, quantiles, 0.01); err != nil {
121 func TestHistogram(t *testing.T) {
122 // Prometheus reports histograms as a count of observations that fell into
123 // each predefined bucket, with the bucket value representing a global upper
124 // limit. That is, the count monotonically increases over the buckets. This
125 // requires a different strategy to test.
127 s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
130 scrape := func() string {
131 resp, _ := http.Get(s.URL)
132 buf, _ := ioutil.ReadAll(resp.Body)
136 namespace, subsystem, name := "test", "prometheus", "histogram"
137 re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `_bucket{x="1",le="([0-9]+|\+Inf)"} ([0-9\.]+)`)
140 bucketMin := (teststat.Mean - (numStdev * teststat.Stdev))
141 bucketMax := (teststat.Mean + (numStdev * teststat.Stdev))
146 bucketDelta := (bucketMax - bucketMin) / bucketCount
147 buckets := []float64{}
148 for i := bucketMin; i <= bucketMax; i += bucketDelta {
149 buckets = append(buckets, float64(i))
152 histogram := NewHistogramFrom(stdprometheus.HistogramOpts{
153 Namespace: namespace,
154 Subsystem: subsystem,
156 Help: "This is the help string for the histogram.",
158 }, []string{"x"}).With("x", "1")
160 // Can't TestHistogram, because Prometheus Histograms don't dynamically
161 // compute quantiles. Instead, they fill up buckets. So, let's populate the
162 // histogram kind of manually.
163 teststat.PopulateNormalHistogram(histogram, rand.Int())
165 // Then, we use ExpectedObservationsLessThan to validate.
166 for _, line := range strings.Split(scrape(), "\n") {
167 match := re.FindStringSubmatch(line)
172 bucket, _ := strconv.ParseInt(match[1], 10, 64)
173 have, _ := strconv.ParseInt(match[2], 10, 64)
175 want := teststat.ExpectedObservationsLessThan(bucket)
176 if match[1] == "+Inf" {
177 want = int64(teststat.Count) // special case
180 // Unfortunately, we observe experimentally that Prometheus is quite
181 // imprecise at the extremes. I'm setting a very high tolerance for now.
182 // It would be great to dig in and figure out whether that's a problem
183 // with my Expected calculation, or in Prometheus.
185 if delta := math.Abs(float64(want) - float64(have)); (delta / float64(want)) > tolerance {
186 t.Errorf("Bucket %d: want %d, have %d (%.1f%%)", bucket, want, have, (100.0 * delta / float64(want)))
191 func TestInconsistentLabelCardinality(t *testing.T) {
195 t.Fatal("expected panic, got none")
199 t.Fatalf("expected error, got %s", reflect.TypeOf(x))
201 if want, have := "inconsistent label cardinality", err.Error(); want != have {
202 t.Fatalf("want %q, have %q", want, have)
206 NewCounterFrom(stdprometheus.CounterOpts{
208 Subsystem: "inconsistent_label_cardinality",
210 Help: "This is the help string for the metric.",
211 }, []string{"a", "b"}).With(
212 "a", "1", "b", "2", "c", "KABOOM!",