Skip to content
This repository has been archived by the owner on Oct 2, 2022. It is now read-only.

Commit

Permalink
Custom label support (#6)
Browse files Browse the repository at this point in the history
Each of the metric methods now allow adding extra labels:

```go
testCounter.Increment(
    net.ParseIP("127.0.0.1"),
    metrics.Label("foo", "bar"),
    metrics.Label("somelabel","somevalue")
)
```

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.
  • Loading branch information
Janos Pasztor authored Dec 14, 2020
1 parent 0b7d33e commit d4ccdf4
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 67 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## 0.9.3: Custom label support

Each of the metric methods now allow adding extra labels:

```go
testCounter.Increment(
net.ParseIP("127.0.0.1"),
metrics.Label("foo", "bar"),
metrics.Label("somelabel","somevalue")
)
```

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.

## 0.9.2: Fixed JSON and YAML marshalling

In the previous version the JSON and YAML configuration marshalling / unmarshalling created an unnecessary sub-map, which was incompatible to ContainerSSH 0.3. This release fixes that and restores compatibility.
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ testCounter.Increment(net.ParseIP("127.0.0.1"))

If you need a metric that can be decremented or set directly you can use the `Gauge` type instead.

### Custom labels

Each of the metric methods allow adding extra labels:

```go
testCounter.Increment(
net.ParseIP("127.0.0.1"),
metrics.Label("foo", "bar"),
metrics.Label("somelabel","somevalue")
)
```

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.

## 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:
Expand Down Expand Up @@ -79,4 +96,5 @@ handler := metrics.NewHandler(
metricsCollector
)
http.ListenAndServe("0.0.0.0:8080", handler)
```
```

111 changes: 93 additions & 18 deletions collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ func (metric Metric) String() string {
metric.Name,
metric.Unit,
metric.Name,
metric.Type)
metric.Type,
)
}

// MetricValue is a structure that contains a value for a specific metric name and set of values.
Expand All @@ -64,6 +65,52 @@ type MetricValue struct {
Value float64
}

// MetricLabel is a struct that can be used with the metrics to pass additional labels. Create it using the
// Label function.
type MetricLabel struct {
name string
value string
}

type metricLabels []MetricLabel

func (labels metricLabels) toMap() map[string]string {
result := map[string]string{}
for _, label := range labels {
result[label.name] = label.value

}
return result
}

// Label creates a MetricLabel for use with the metric functions. Panics if name or value are empty. The name "country"
// is reserved.
func Label(name string, value string) MetricLabel {
if name == "" {
panic("BUG: the name cannot be empty")
}
if name == "country" {
panic("BUG: the name 'country' is reserved for GeoIP lookups")
}
if value == "" {
panic("BUG: the value cannot be empty")
}
return MetricLabel{
name: name,
value: value,
}
}

// Name returns the name of the label.
func (m MetricLabel) Name() string {
return m.name
}

// Value returns the value of the label.
func (m MetricLabel) Value() string {
return m.value
}

// CombinedName returns the name and labels combined.
func (metricValue MetricValue) CombinedName() string {
var labelList []string
Expand All @@ -74,9 +121,9 @@ func (metricValue MetricValue) CombinedName() string {
}
sort.Strings(keys)

replacer := strings.NewReplacer(`"`, `\"`, `\`, `\\`)
for _, k := range keys {
// TODO escaping
labelList = append(labelList, k+"=\""+metricValue.Labels[k]+"\"")
labelList = append(labelList, k+"=\""+replacer.Replace(metricValue.Labels[k])+"\"")
}

var labels string
Expand Down Expand Up @@ -130,56 +177,84 @@ type Collector interface {
// SimpleCounter is a simple counter that can only be incremented.
type SimpleCounter interface {
// Increment increments the counter by 1
Increment()
//
// - labels is a set of labels to apply. Can be created using the Label function.
Increment(labels ...MetricLabel)

// IncrementBy increments the counter by the specified number. Only returns an error if the passed by parameter is
// negative.
IncrementBy(by float64) error
//
// - labels is a set of labels to apply. Can be created using the Label function.
IncrementBy(by float64, labels ...MetricLabel) error
}

// SimpleGeoCounter is a simple counter that can only be incremented and is labeled with the country from a GeoIP
// lookup.
type SimpleGeoCounter interface {
// Increment increments the counter for the country from the specified ip by 1.
Increment(ip net.IP)
//
// - labels is a set of labels to apply. Can be created using the Label function.
Increment(ip net.IP, labels ...MetricLabel)

// IncrementBy increments the counter for the country from the specified ip by the specified value.
// Only returns an error if the passed by parameter is negative.
IncrementBy(ip net.IP, by float64) error
//
// - labels is a set of labels to apply. Can be created using the Label function.
IncrementBy(ip net.IP, by float64, labels ...MetricLabel) error
}

// SimpleGauge is a metric that can be incremented and decremented.
type SimpleGauge interface {
// Increment increments the counter by 1
Increment()
// Increment increments the counter by 1.
//
// - labels is a set of labels to apply. Can be created using the Label function.
Increment(labels ...MetricLabel)

// IncrementBy increments the counter by the specified number.
IncrementBy(by float64)
//
// - labels is a set of labels to apply. Can be created using the Label function.
IncrementBy(by float64, labels ...MetricLabel)

// Decrement decreases the metric by 1.
Decrement()
//
// - labels is a set of labels to apply. Can be created using the Label function.
Decrement(labels ...MetricLabel)

// Decrement decreases the metric by the specified value.
DecrementBy(by float64)
//
// - labels is a set of labels to apply. Can be created using the Label function.
DecrementBy(by float64, labels ...MetricLabel)

// Set sets the value of the metric to an exact value.
Set(value float64)
//
// - labels is a set of labels to apply. Can be created using the Label function.
Set(value float64, labels ...MetricLabel)
}

// 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.
Increment(ip net.IP)
//
// - labels is a set of labels to apply. Can be created using the Label function.
Increment(ip net.IP, labels ...MetricLabel)

// IncrementBy increments the counter for the country from the specified ip by the specified value.
IncrementBy(ip net.IP, by float64)
//
// - labels is a set of labels to apply. Can be created using the Label function.
IncrementBy(ip net.IP, by float64, labels ...MetricLabel)

// Decrement decreases the value for the country looked up from the specified IP by 1.
Decrement(ip net.IP)
//
// - labels is a set of labels to apply. Can be created using the Label function.
Decrement(ip net.IP, labels ...MetricLabel)

// DecrementBy decreases the value for the country looked up from the specified IP by the specified value.
DecrementBy(ip net.IP, by float64)
//
// - labels is a set of labels to apply. Can be created using the Label function.
DecrementBy(ip net.IP, by float64, labels ...MetricLabel)

// Set sets the value of the metric for the country looked up from the specified IP.
Set(ip net.IP, value float64)
//
// - labels is a set of labels to apply. Can be created using the Label function.
Set(ip net.IP, value float64, labels ...MetricLabel)
}
11 changes: 6 additions & 5 deletions counter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ type counterImpl struct {
collector *collector
}

func (c *counterImpl) Increment() {
_ = c.IncrementBy(1)
func (c *counterImpl) Increment(labels ...MetricLabel) {
_ = c.IncrementBy(1, labels...)
}

func (c *counterImpl) IncrementBy(by float64) error {
func (c *counterImpl) IncrementBy(by float64, labels ...MetricLabel) error {
c.collector.mutex.Lock()
defer c.collector.mutex.Unlock()

if by < 0 {
return CounterCannotBeIncrementedByNegative
}

value := c.collector.get(c.name, map[string]string{})
c.collector.set(c.name, map[string]string{}, value+by)
realLabels := metricLabels(labels).toMap()
value := c.collector.get(c.name, realLabels)
c.collector.set(c.name, realLabels, value+by)
return nil
}
10 changes: 10 additions & 0 deletions counter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,14 @@ func TestCounter(t *testing.T) {
metrics.CounterCannotBeIncrementedByNegative.Error(),
"incrementing a counter by negative number did not return an error",
)

counter.Increment(metrics.Label("foo", "bar"))
metric = collector.GetMetric("test")
for _, m := range metric {
if m.CombinedName() == "test{foo=\"bar\"}" {
assert.Equal(t, float64(1), m.Value)
} else {
assert.Equal(t, float64(4), m.Value)
}
}
}
15 changes: 7 additions & 8 deletions countergeo.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,22 @@ type counterGeoImpl struct {
collector *collector
}

func (c *counterGeoImpl) Increment(ip net.IP) {
_ = c.IncrementBy(ip, 1)
func (c *counterGeoImpl) Increment(ip net.IP, labels ...MetricLabel) {
_ = c.IncrementBy(ip, 1, labels...)
}

func (c *counterGeoImpl) IncrementBy(ip net.IP, by float64) error {
func (c *counterGeoImpl) IncrementBy(ip net.IP, by float64, labels ...MetricLabel) error {
c.collector.mutex.Lock()
defer c.collector.mutex.Unlock()

if by < 0 {
return CounterCannotBeIncrementedByNegative
}

labels := map[string]string{
"country": c.collector.geoIpLookupProvider.Lookup(ip),
}
realLabels := metricLabels(labels).toMap()
realLabels["country"] = c.collector.geoIpLookupProvider.Lookup(ip)

value := c.collector.get(c.name, labels)
c.collector.set(c.name, labels, value+by)
value := c.collector.get(c.name, realLabels)
c.collector.set(c.name, realLabels, value+by)
return nil
}
8 changes: 8 additions & 0 deletions countergeo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,12 @@ func TestCounterGeo(t *testing.T) {
assert.Equal(t, 2, len(metric))
assert.Equal(t, float64(1), metric[0].Value)
assert.Equal(t, float64(1), metric[1].Value)

counter.Increment(net.ParseIP("127.0.0.2"), metrics.Label("foo", "bar"))
metric = collector.GetMetric("test")
for _, m := range metric {
if m.CombinedName() == "test{country=\"XX\",foo=\"bar\"}" {
assert.Equal(t, float64(1), m.Value)
}
}
}
27 changes: 15 additions & 12 deletions gauge.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,36 @@ type gaugeImpl struct {
collector *collector
}

func (g *gaugeImpl) Increment() {
g.IncrementBy(1)
func (g *gaugeImpl) Increment(labels ...MetricLabel) {
g.IncrementBy(1, labels...)
}

func (g *gaugeImpl) IncrementBy(by float64) {
func (g *gaugeImpl) IncrementBy(by float64, labels ...MetricLabel) {
g.collector.mutex.Lock()
defer g.collector.mutex.Unlock()

value := g.collector.get(g.name, map[string]string{})
g.collector.set(g.name, map[string]string{}, value+by)
realLabels := metricLabels(labels).toMap()
value := g.collector.get(g.name, realLabels)
g.collector.set(g.name, realLabels, value+by)
}

func (g *gaugeImpl) Decrement() {
g.DecrementBy(1)
func (g *gaugeImpl) Decrement(labels ...MetricLabel) {
g.DecrementBy(1, labels...)
}

func (g *gaugeImpl) DecrementBy(by float64) {
func (g *gaugeImpl) DecrementBy(by float64, labels ...MetricLabel) {
g.collector.mutex.Lock()
defer g.collector.mutex.Unlock()

value := g.collector.get(g.name, map[string]string{})
g.collector.set(g.name, map[string]string{}, value-by)
realLabels := metricLabels(labels).toMap()
value := g.collector.get(g.name, realLabels)
g.collector.set(g.name, realLabels, value-by)
}

func (g *gaugeImpl) Set(value float64) {
func (g *gaugeImpl) Set(value float64, labels ...MetricLabel) {
g.collector.mutex.Lock()
defer g.collector.mutex.Unlock()

g.collector.set(g.name, map[string]string{}, value)
realLabels := metricLabels(labels).toMap()
g.collector.set(g.name, realLabels, value)
}
Loading

0 comments on commit d4ccdf4

Please sign in to comment.