From e923fd780fdcf34171f4a55b3cd99bc377cfb7e6 Mon Sep 17 00:00:00 2001 From: Janos Pasztor Date: Tue, 29 Dec 2020 01:00:52 +0100 Subject: [PATCH] 0.9.5: Added extended metrics (#9) This release adds a `WithLabel` method to create metrics primed with certain labels. This can be used when passing labels between modules. --- CHANGELOG.md | 4 ++++ README.md | 2 ++ collector.go | 52 +++++++++++++++++++++++++++++++++++++++------- collector_impl.go | 16 +++++++------- counter.go | 13 +++++++++++- counter_test.go | 21 +++++++++++++++++++ countergeo.go | 13 +++++++++++- countergeo_test.go | 21 +++++++++++++++++++ gauge.go | 21 ++++++++++++++++--- gauge_test.go | 32 ++++++++++++++++++++++++++++ gaugegeo.go | 21 ++++++++++++++++--- gaugegeo_test.go | 33 +++++++++++++++++++++++++++++ 12 files changed, 225 insertions(+), 24 deletions(-) create mode 100644 gauge_test.go create mode 100644 gaugegeo_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5787c23..b08bbe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.9.5: Added extended metrics + +This release adds a `WithLabel` method to create metrics primed with certain labels. This can be used when passing labels between modules. + ## 0.9.4: Add `Must*` methods This release adds methods starting with `Must` that panic instead of throwing an error. diff --git a/README.md b/README.md index 8863285..0f0e614 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ The following rules apply and will cause a `panic` if violated: - Label names and values cannot be empty. - The `country` label name is reserved for GeoIP usage. +The metrics also have a `WithLabels()` method that allow for creating a copy of a metric already primed with a set of labels. This can be used when passing metrics to other modules that need to be scoped. + ## Using the metrics server The metrics server exposes the collected metrics on an HTTP webserver in the Prometheus / OpenMetrics format. It requires the [service library](https://github.com/containerssh/service) and a logger from the [log library](https://github.com/containerssh/log) to work properly: diff --git a/collector.go b/collector.go index 8d26239..3f34473 100644 --- a/collector.go +++ b/collector.go @@ -151,34 +151,34 @@ var CounterCannotBeIncrementedByNegative = errors.New("a counter cannot be incre // Collector is the main interface for interacting with the metrics collector. type Collector interface { // CreateCounter creates a monotonic (increasing) counter with the specified name and help text. - CreateCounter(name string, unit string, help string) (SimpleCounter, error) + CreateCounter(name string, unit string, help string) (Counter, error) // MustCreateCounter creates a monotonic (increasing) counter with the specified name and help text. Panics if an // error occurs. - MustCreateCounter(name string, unit string, help string) SimpleCounter + MustCreateCounter(name string, unit string, help string) Counter // CreateCounterGeo creates a monotonic (increasing) counter that is labeled with the country from the GeoIP lookup // with the specified name and help text. - CreateCounterGeo(name string, unit string, help string) (SimpleGeoCounter, error) + CreateCounterGeo(name string, unit string, help string) (GeoCounter, error) // MustCreateCounterGeo creates a monotonic (increasing) counter that is labeled with the country from the GeoIP // lookup with the specified name and help text. Panics if an error occurs. - MustCreateCounterGeo(name string, unit string, help string) SimpleGeoCounter + MustCreateCounterGeo(name string, unit string, help string) GeoCounter // CreateGauge creates a freely modifiable numeric gauge with the specified name and help text. - CreateGauge(name string, unit string, help string) (SimpleGauge, error) + CreateGauge(name string, unit string, help string) (Gauge, error) // MustCreateGauge creates a freely modifiable numeric gauge with the specified name and help text. Panics if an // error occurs. - MustCreateGauge(name string, unit string, help string) SimpleGauge + MustCreateGauge(name string, unit string, help string) Gauge // CreateGaugeGeo creates a freely modifiable numeric gauge that is labeled with the country from the GeoIP lookup // with the specified name and help text. - CreateGaugeGeo(name string, unit string, help string) (SimpleGeoGauge, error) + CreateGaugeGeo(name string, unit string, help string) (GeoGauge, error) // MustCreateGaugeGeo creates a freely modifiable numeric gauge that is labeled with the country from the GeoIP // lookup with the specified name and help text. Panics if an error occurs. - MustCreateGaugeGeo(name string, unit string, help string) SimpleGeoGauge + MustCreateGaugeGeo(name string, unit string, help string) GeoGauge // ListMetrics returns a list of metrics metadata stored in the collector. ListMetrics() []Metric @@ -204,6 +204,15 @@ type SimpleCounter interface { IncrementBy(by float64, labels ...MetricLabel) error } +// Counter extends the SimpleCounter interface by adding a WithLabels function to create a copy of the counter that +// is primed with a set of labels. +type Counter interface { + SimpleCounter + + // WithLabels adds labels to the counter + WithLabels(labels ...MetricLabel) Counter +} + // SimpleGeoCounter is a simple counter that can only be incremented and is labeled with the country from a GeoIP // lookup. type SimpleGeoCounter interface { @@ -219,6 +228,15 @@ type SimpleGeoCounter interface { IncrementBy(ip net.IP, by float64, labels ...MetricLabel) error } +// GeoCounter extends the SimpleGeoCounter interface by adding a WithLabels function to create a copy of the counter +// that is primed with a set of labels. +type GeoCounter interface { + SimpleGeoCounter + + // WithLabels adds labels to the counter + WithLabels(labels ...MetricLabel) GeoCounter +} + // SimpleGauge is a metric that can be incremented and decremented. type SimpleGauge interface { // Increment increments the counter by 1. @@ -247,6 +265,15 @@ type SimpleGauge interface { Set(value float64, labels ...MetricLabel) } +// Gauge extends the SimpleGauge interface by adding a WithLabels function to create a copy of the counter +// that is primed with a set of labels. +type Gauge interface { + SimpleGauge + + // WithLabels adds labels to the counter + WithLabels(labels ...MetricLabel) Gauge +} + // SimpleGeoGauge is a metric that can be incremented and decremented and is labeled by the country from a GeoIP lookup. type SimpleGeoGauge interface { // Increment increments the counter for the country from the specified ip by 1. @@ -274,3 +301,12 @@ type SimpleGeoGauge interface { // - labels is a set of labels to apply. Can be created using the Label function. Set(ip net.IP, value float64, labels ...MetricLabel) } + +// GeoGauge extends the SimpleGeoGauge interface by adding a WithLabels function to create a copy of the counter +// that is primed with a set of labels. +type GeoGauge interface { + SimpleGeoGauge + + // WithLabels adds labels to the counter + WithLabels(labels ...MetricLabel) GeoGauge +} diff --git a/collector_impl.go b/collector_impl.go index 764aff9..f913cb4 100644 --- a/collector_impl.go +++ b/collector_impl.go @@ -16,7 +16,7 @@ type collector struct { values map[string]*metricValue } -func (c *collector) MustCreateCounter(name string, unit string, help string) SimpleCounter { +func (c *collector) MustCreateCounter(name string, unit string, help string) Counter { counter, err := c.CreateCounter(name, unit, help) if err != nil { panic(err) @@ -24,7 +24,7 @@ func (c *collector) MustCreateCounter(name string, unit string, help string) Sim return counter } -func (c *collector) MustCreateCounterGeo(name string, unit string, help string) SimpleGeoCounter { +func (c *collector) MustCreateCounterGeo(name string, unit string, help string) GeoCounter { counter, err := c.CreateCounterGeo(name, unit, help) if err != nil { panic(err) @@ -32,7 +32,7 @@ func (c *collector) MustCreateCounterGeo(name string, unit string, help string) return counter } -func (c *collector) MustCreateGauge(name string, unit string, help string) SimpleGauge { +func (c *collector) MustCreateGauge(name string, unit string, help string) Gauge { gauge, err := c.CreateGauge(name, unit, help) if err != nil { panic(err) @@ -40,7 +40,7 @@ func (c *collector) MustCreateGauge(name string, unit string, help string) Simpl return gauge } -func (c *collector) MustCreateGaugeGeo(name string, unit string, help string) SimpleGeoGauge { +func (c *collector) MustCreateGaugeGeo(name string, unit string, help string) GeoGauge { gauge, err := c.CreateGaugeGeo(name, unit, help) if err != nil { panic(err) @@ -72,7 +72,7 @@ func (c *collector) createMetric(name string, unit string, help string, metricTy return nil } -func (c *collector) CreateCounter(name string, unit string, help string) (SimpleCounter, error) { +func (c *collector) CreateCounter(name string, unit string, help string) (Counter, error) { if err := c.createMetric(name, unit, help, MetricTypeCounter); err != nil { return nil, err } @@ -82,7 +82,7 @@ func (c *collector) CreateCounter(name string, unit string, help string) (Simple }, nil } -func (c *collector) CreateCounterGeo(name string, unit string, help string) (SimpleGeoCounter, error) { +func (c *collector) CreateCounterGeo(name string, unit string, help string) (GeoCounter, error) { if err := c.createMetric(name, unit, help, MetricTypeCounter); err != nil { return nil, err } @@ -92,7 +92,7 @@ func (c *collector) CreateCounterGeo(name string, unit string, help string) (Sim }, nil } -func (c *collector) CreateGauge(name string, unit string, help string) (SimpleGauge, error) { +func (c *collector) CreateGauge(name string, unit string, help string) (Gauge, error) { if err := c.createMetric(name, unit, help, MetricTypeGauge); err != nil { return nil, err } @@ -102,7 +102,7 @@ func (c *collector) CreateGauge(name string, unit string, help string) (SimpleGa }, nil } -func (c *collector) CreateGaugeGeo(name string, unit string, help string) (SimpleGeoGauge, error) { +func (c *collector) CreateGaugeGeo(name string, unit string, help string) (GeoGauge, error) { if err := c.createMetric(name, unit, help, MetricTypeGauge); err != nil { return nil, err } diff --git a/counter.go b/counter.go index 316e47f..908d3ab 100644 --- a/counter.go +++ b/counter.go @@ -3,6 +3,7 @@ package metrics type counterImpl struct { name string collector *collector + labels []MetricLabel } func (c *counterImpl) Increment(labels ...MetricLabel) { @@ -17,8 +18,18 @@ func (c *counterImpl) IncrementBy(by float64, labels ...MetricLabel) error { return CounterCannotBeIncrementedByNegative } - realLabels := metricLabels(labels).toMap() + realLabels := metricLabels( + append(c.labels, labels...), + ).toMap() value := c.collector.get(c.name, realLabels) c.collector.set(c.name, realLabels, value+by) return nil } + +func (c *counterImpl) WithLabels(labels ...MetricLabel) Counter { + return &counterImpl{ + name: c.name, + collector: c.collector, + labels: append(c.labels, labels...), + } +} diff --git a/counter_test.go b/counter_test.go index 8ce0011..7e1a065 100644 --- a/counter_test.go +++ b/counter_test.go @@ -62,3 +62,24 @@ func TestCounter(t *testing.T) { } } } + +// TestCounter tests the functionality of counters +func TestCounterLabel(t *testing.T) { + collector := metrics.New(&geoIpLookupProvider{}) + counter, err := collector.CreateCounter("test", "seconds", "Hello world!") + assert.Nil(t, err, "creating counter returned an error") + counter.Increment() + newCounter := counter.WithLabels(metrics.Label("foo", "bar")) + newCounter.Increment(metrics.Label("baz", "bar")) + + metric := collector.GetMetric("test") + assert.Equal(t, 2, len(metric)) + + assert.Equal(t, "test", metric[0].Name) + assert.Equal(t, float64(1), metric[0].Value) + assert.Equal(t, 0, len(metric[0].Labels)) + + assert.Equal(t, "test", metric[1].Name) + assert.Equal(t, float64(1), metric[1].Value) + assert.Equal(t, 2, len(metric[1].Labels)) +} diff --git a/countergeo.go b/countergeo.go index 326da52..5130701 100644 --- a/countergeo.go +++ b/countergeo.go @@ -7,6 +7,7 @@ import ( type counterGeoImpl struct { name string collector *collector + labels []MetricLabel } func (c *counterGeoImpl) Increment(ip net.IP, labels ...MetricLabel) { @@ -21,10 +22,20 @@ func (c *counterGeoImpl) IncrementBy(ip net.IP, by float64, labels ...MetricLabe return CounterCannotBeIncrementedByNegative } - realLabels := metricLabels(labels).toMap() + realLabels := metricLabels( + append(c.labels, labels...), + ).toMap() realLabels["country"] = c.collector.geoIpLookupProvider.Lookup(ip) value := c.collector.get(c.name, realLabels) c.collector.set(c.name, realLabels, value+by) return nil } + +func (c *counterGeoImpl) WithLabels(labels ...MetricLabel) GeoCounter { + return &counterGeoImpl{ + name: c.name, + collector: c.collector, + labels: append(c.labels, labels...), + } +} diff --git a/countergeo_test.go b/countergeo_test.go index 84a8748..e84ade0 100644 --- a/countergeo_test.go +++ b/countergeo_test.go @@ -53,3 +53,24 @@ func TestCounterGeo(t *testing.T) { } } } + +// TestCounter tests the functionality of counters +func TestGeoCounterLabel(t *testing.T) { + collector := metrics.New(&geoIpLookupProvider{}) + counter, err := collector.CreateCounterGeo("test", "seconds", "Hello world!") + assert.Nil(t, err, "creating counter returned an error") + counter.Increment(net.ParseIP("127.0.0.2")) + newCounter := counter.WithLabels(metrics.Label("foo", "bar")) + newCounter.Increment(net.ParseIP("127.0.0.2"), metrics.Label("baz", "bar")) + + metric := collector.GetMetric("test") + assert.Equal(t, 2, len(metric)) + + assert.Equal(t, "test", metric[0].Name) + assert.Equal(t, float64(1), metric[0].Value) + assert.Equal(t, 1, len(metric[0].Labels)) + + assert.Equal(t, "test", metric[1].Name) + assert.Equal(t, float64(1), metric[1].Value) + assert.Equal(t, 3, len(metric[1].Labels)) +} diff --git a/gauge.go b/gauge.go index 21352a0..c0fd850 100644 --- a/gauge.go +++ b/gauge.go @@ -3,6 +3,7 @@ package metrics type gaugeImpl struct { name string collector *collector + labels []MetricLabel } func (g *gaugeImpl) Increment(labels ...MetricLabel) { @@ -13,7 +14,9 @@ func (g *gaugeImpl) IncrementBy(by float64, labels ...MetricLabel) { g.collector.mutex.Lock() defer g.collector.mutex.Unlock() - realLabels := metricLabels(labels).toMap() + realLabels := metricLabels( + append(g.labels, labels...), + ).toMap() value := g.collector.get(g.name, realLabels) g.collector.set(g.name, realLabels, value+by) } @@ -26,7 +29,9 @@ func (g *gaugeImpl) DecrementBy(by float64, labels ...MetricLabel) { g.collector.mutex.Lock() defer g.collector.mutex.Unlock() - realLabels := metricLabels(labels).toMap() + realLabels := metricLabels( + append(g.labels, labels...), + ).toMap() value := g.collector.get(g.name, realLabels) g.collector.set(g.name, realLabels, value-by) } @@ -35,6 +40,16 @@ func (g *gaugeImpl) Set(value float64, labels ...MetricLabel) { g.collector.mutex.Lock() defer g.collector.mutex.Unlock() - realLabels := metricLabels(labels).toMap() + realLabels := metricLabels( + append(g.labels, labels...), + ).toMap() g.collector.set(g.name, realLabels, value) } + +func (g *gaugeImpl) WithLabels(labels ...MetricLabel) Gauge { + return &gaugeImpl{ + name: g.name, + collector: g.collector, + labels: append(g.labels, labels...), + } +} diff --git a/gauge_test.go b/gauge_test.go new file mode 100644 index 0000000..65c6454 --- /dev/null +++ b/gauge_test.go @@ -0,0 +1,32 @@ +package metrics_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/containerssh/metrics" +) + +func TestGauge(t *testing.T) { + collector := metrics.New(&geoIpLookupProvider{}) + gauge, err := collector.CreateGauge("test", "seconds", "Hello world!") + assert.Nil(t, err, "creating counter returned an error") + + gauge.Set(42) + + testMetrics := collector.GetMetric("test") + assert.Equal(t, 1, len(testMetrics)) + assert.Equal(t, 0, len(testMetrics[0].Labels)) + assert.Equal(t, float64(42), testMetrics[0].Value) + + newGauge := gauge.WithLabels(metrics.Label("foo", "bar")) + newGauge.Set(43) + + testMetrics = collector.GetMetric("test") + assert.Equal(t, 2, len(testMetrics)) + assert.Equal(t, 0, len(testMetrics[0].Labels)) + assert.Equal(t, 1, len(testMetrics[1].Labels)) + assert.Equal(t, float64(42), testMetrics[0].Value) + assert.Equal(t, float64(43), testMetrics[1].Value) +} diff --git a/gaugegeo.go b/gaugegeo.go index d666758..d27ae0e 100644 --- a/gaugegeo.go +++ b/gaugegeo.go @@ -7,6 +7,7 @@ import ( type gaugeGeoImpl struct { name string collector *collector + labels []MetricLabel } func (g *gaugeGeoImpl) Increment(ip net.IP, labels ...MetricLabel) { @@ -17,7 +18,9 @@ func (g *gaugeGeoImpl) IncrementBy(ip net.IP, by float64, labels ...MetricLabel) g.collector.mutex.Lock() defer g.collector.mutex.Unlock() - realLabels := metricLabels(labels).toMap() + realLabels := metricLabels( + append(g.labels, labels...), + ).toMap() realLabels["country"] = g.collector.geoIpLookupProvider.Lookup(ip) value := g.collector.get(g.name, realLabels) @@ -32,7 +35,9 @@ func (g *gaugeGeoImpl) DecrementBy(ip net.IP, by float64, labels ...MetricLabel) g.collector.mutex.Lock() defer g.collector.mutex.Unlock() - realLabels := metricLabels(labels).toMap() + realLabels := metricLabels( + append(g.labels, labels...), + ).toMap() realLabels["country"] = g.collector.geoIpLookupProvider.Lookup(ip) value := g.collector.get(g.name, realLabels) @@ -43,8 +48,18 @@ func (g *gaugeGeoImpl) Set(ip net.IP, value float64, labels ...MetricLabel) { g.collector.mutex.Lock() defer g.collector.mutex.Unlock() - realLabels := metricLabels(labels).toMap() + realLabels := metricLabels( + append(g.labels, labels...), + ).toMap() realLabels["country"] = g.collector.geoIpLookupProvider.Lookup(ip) g.collector.set(g.name, realLabels, value) } + +func (g *gaugeGeoImpl) WithLabels(labels ...MetricLabel) GeoGauge { + return &gaugeGeoImpl{ + name: g.name, + collector: g.collector, + labels: append(g.labels, labels...), + } +} diff --git a/gaugegeo_test.go b/gaugegeo_test.go new file mode 100644 index 0000000..e38a4ad --- /dev/null +++ b/gaugegeo_test.go @@ -0,0 +1,33 @@ +package metrics_test + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/containerssh/metrics" +) + +func TestGaugeGeo(t *testing.T) { + collector := metrics.New(&geoIpLookupProvider{}) + gauge, err := collector.CreateGaugeGeo("test", "seconds", "Hello world!") + assert.Nil(t, err, "creating counter returned an error") + + gauge.Set(net.ParseIP("127.0.0.1"), 42) + + testMetrics := collector.GetMetric("test") + assert.Equal(t, 1, len(testMetrics)) + assert.Equal(t, 1, len(testMetrics[0].Labels)) + assert.Equal(t, float64(42), testMetrics[0].Value) + + newGauge := gauge.WithLabels(metrics.Label("foo", "bar")) + newGauge.Set(net.ParseIP("127.0.0.1"), 43) + + testMetrics = collector.GetMetric("test") + assert.Equal(t, 2, len(testMetrics)) + assert.Equal(t, 1, len(testMetrics[0].Labels)) + assert.Equal(t, 2, len(testMetrics[1].Labels)) + assert.Equal(t, float64(42), testMetrics[0].Value) + assert.Equal(t, float64(43), testMetrics[1].Value) +}