/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package prometheus_test import ( "fmt" "io/ioutil" "net/http" "net/http/httptest" commonmetrics "github.com/hyperledger/fabric/common/metrics" "github.com/hyperledger/fabric/common/metrics/prometheus" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" prom "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) var _ = Describe("Provider", func() { var ( server *httptest.Server client *http.Client p *prometheus.Provider ) BeforeEach(func() { // Note: These tests can't be run in parallel because go-kit uses // the global registry to manage metrics. This is something to revisit // in the future. registry := prom.NewRegistry() prom.DefaultRegisterer = registry prom.DefaultGatherer = registry server = httptest.NewServer(promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) client = server.Client() p = &prometheus.Provider{} }) AfterEach(func() { server.Close() }) It("implements metrics.Provider", func() { var p commonmetrics.Provider = &prometheus.Provider{} Expect(p).NotTo(BeNil()) }) Describe("NewCounter", func() { var counterOpts commonmetrics.CounterOpts BeforeEach(func() { counterOpts = commonmetrics.CounterOpts{ Namespace: "peer", Subsystem: "playground", Name: "counter_name", Help: "This is some help text for the counter", LabelNames: []string{"alpha", "beta"}, } }) It("creates counters that support labels", func() { counter := p.NewCounter(counterOpts) counter.With("alpha", "a", "beta", "b").Add(1) counter.With("alpha", "aardvark", "beta", "b").Add(2) resp, err := client.Get(fmt.Sprintf("http://%s/metrics", server.Listener.Addr().String())) Expect(err).NotTo(HaveOccurred()) defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) Expect(err).NotTo(HaveOccurred()) Expect(string(bytes)).To(ContainSubstring(`# HELP peer_playground_counter_name This is some help text for the counter`)) Expect(string(bytes)).To(ContainSubstring(`# TYPE peer_playground_counter_name counter`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_counter_name{alpha="a",beta="b"} 1`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_counter_name{alpha="aardvark",beta="b"} 2`)) }) Context("when the counter is defined without labels", func() { BeforeEach(func() { counterOpts.LabelNames = nil }) It("With does not need to be called", func() { counter := p.NewCounter(counterOpts) counter.Add(1) resp, err := client.Get(fmt.Sprintf("http://%s/metrics", server.Listener.Addr().String())) Expect(err).NotTo(HaveOccurred()) defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) Expect(err).NotTo(HaveOccurred()) Expect(string(bytes)).To(ContainSubstring(`peer_playground_counter_name 1`)) }) }) }) Describe("NewGauge", func() { var gaugeOpts commonmetrics.GaugeOpts BeforeEach(func() { gaugeOpts = commonmetrics.GaugeOpts{ Namespace: "peer", Subsystem: "playground", Name: "gauge_name", Help: "This is some help text for the gauge", LabelNames: []string{"alpha", "beta"}, } }) It("creates gauges that support labels", func() { gauge := p.NewGauge(gaugeOpts) gauge.With("alpha", "a", "beta", "b").Add(1) gauge.With("alpha", "a", "beta", "b").Add(1) gauge.With("alpha", "aardvark", "beta", "b").Add(1) gauge.With("alpha", "aardvark", "beta", "bob").Set(99) resp, err := client.Get(fmt.Sprintf("http://%s/metrics", server.Listener.Addr().String())) Expect(err).NotTo(HaveOccurred()) defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) Expect(err).NotTo(HaveOccurred()) Expect(string(bytes)).To(ContainSubstring(`# HELP peer_playground_gauge_name This is some help text for the gauge`)) Expect(string(bytes)).To(ContainSubstring(`# TYPE peer_playground_gauge_name gauge`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_gauge_name{alpha="a",beta="b"} 2`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_gauge_name{alpha="aardvark",beta="b"} 1`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_gauge_name{alpha="aardvark",beta="bob"} 99`)) }) }) Describe("NewHistogram", func() { var histogramOpts commonmetrics.HistogramOpts BeforeEach(func() { histogramOpts = commonmetrics.HistogramOpts{ Namespace: "peer", Subsystem: "playground", Name: "histogram_name", Help: "This is some help text for the gauge", LabelNames: []string{"alpha", "beta"}, } }) It("creates histogram that support labels", func() { histogram := p.NewHistogram(histogramOpts) for _, limit := range prom.DefBuckets { histogram.With("alpha", "a", "beta", "b").Observe(limit) } histogram.With("alpha", "a", "beta", "b").Observe(prom.DefBuckets[len(prom.DefBuckets)-1] + 1) resp, err := client.Get(fmt.Sprintf("http://%s/metrics", server.Listener.Addr().String())) Expect(err).NotTo(HaveOccurred()) defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) Expect(err).NotTo(HaveOccurred()) Expect(string(bytes)).To(ContainSubstring(`# HELP peer_playground_histogram_name This is some help text for the gauge`)) Expect(string(bytes)).To(ContainSubstring(`# TYPE peer_playground_histogram_name histogram`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="0.005"} 1`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="0.01"} 2`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="0.025"} 3`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="0.05"} 4`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="0.1"} 5`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="0.25"} 6`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="0.5"} 7`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="1"} 8`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="2.5"} 9`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="5"} 10`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="10"} 11`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="+Inf"} 12`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_sum{alpha="a",beta="b"} `)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_count{alpha="a",beta="b"} 12`)) }) It("creates histogram with buckets that support labels", func() { histogramOpts.Buckets = []float64{1, 5} histogram := p.NewHistogram(histogramOpts) histogram.With("alpha", "a", "beta", "b").Observe(0.5) histogram.With("alpha", "a", "beta", "b").Observe(4.5) resp, err := client.Get(fmt.Sprintf("http://%s/metrics", server.Listener.Addr().String())) Expect(err).NotTo(HaveOccurred()) defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) Expect(err).NotTo(HaveOccurred()) Expect(string(bytes)).To(ContainSubstring(`# HELP peer_playground_histogram_name This is some help text for the gauge`)) Expect(string(bytes)).To(ContainSubstring(`# TYPE peer_playground_histogram_name histogram`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="1"} 1`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_bucket{alpha="a",beta="b",le="5"} 2`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_sum{alpha="a",beta="b"} 5`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_histogram_name_count{alpha="a",beta="b"} 2`)) }) }) // This helps ensure the label cardinality behavior matches what was implemented // for statsd. If these tests fail, correspending updates will be needed in the label // processing used for statsd. Describe("edge case behavior", func() { var counterOpts commonmetrics.CounterOpts BeforeEach(func() { counterOpts = commonmetrics.CounterOpts{ Namespace: "peer", Subsystem: "playground", Name: "counter_name", Help: "This is some help text for the counter", LabelNames: []string{"alpha", "beta"}, } }) Context("when With is called without a label value", func() { It("uses unknown for the missing value", func() { counter := p.NewCounter(counterOpts) counter.With("alpha", "a", "beta").Add(1) resp, err := client.Get(fmt.Sprintf("http://%s/metrics", server.Listener.Addr().String())) Expect(err).NotTo(HaveOccurred()) defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) Expect(err).NotTo(HaveOccurred()) Expect(string(bytes)).To(ContainSubstring(`# HELP peer_playground_counter_name This is some help text for the counter`)) Expect(string(bytes)).To(ContainSubstring(`# TYPE peer_playground_counter_name counter`)) Expect(string(bytes)).To(ContainSubstring(`peer_playground_counter_name{alpha="a",beta="unknown"} 1`)) }) }) Context("when With is called with an extra label", func() { It("panics", func() { counter := p.NewCounter(counterOpts) panicMessage := func() (panicMessage interface{}) { defer func() { panicMessage = recover() }() counter.With("alpha", "a", "beta", "b", "charlie", "c").Add(1) return }() Expect(panicMessage).To(MatchError(MatchRegexp(`inconsistent label cardinality: expected 2 label values but got 3 in prometheus.Labels\{.*\}`))) }) }) Context("when label values are not provided", func() { It("it panics with a cardinaility message", func() { counter := p.NewCounter(counterOpts) panicMessage := func() (panicMessage interface{}) { defer func() { panicMessage = recover() }() counter.Add(1) return }() Expect(panicMessage).To(MatchError(`inconsistent label cardinality: expected 2 label values but got 0 in prometheus.Labels{}`)) }) }) }) })