Testing Golang Prometheus Instrumentation

Instrumenting an application means collecting data points to further analyze its performance, diagnose errors and understand behaviour. These measurements will help you answer questions like:

Being able to answer these and many other questions in a production ready app is essential. It helps everyone responsible for the code to make better decisions, prioritize features and support necessary changes. All these benefits are only possible if you trust your metrics, if you are sure they represent reality.

One way to be confident about your software, and thus your metrics, is to test the code generating them and this is what we will cover today.

Prometheus Metrics

Prometheus is one of the leading time series databases, often used to store data representing metrics. The Internet is full of articles on its features, usage, deployment etc, so I won't be covering the basics here. If you are new to Prometheus, I recommend reading these pieces:

Testing Prometheus Metrics with Golang

package main

import (
    "net/http"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    registry = prometheus.NewRegistry()

    simpleCounterTotal = promauto.NewCounter(prometheus.CounterOpts{
        Namespace: "myapp",
        Name:      "simple_counter_total",
    })

    temperature = promauto.NewGaugeVec(prometheus.GaugeOpts{
        Namespace: "myapp",
        Name:      "temperature",
    }, []string{"city"})
)

func init() {
    registry.MustRegister(simpleCounterTotal, temperature)
}

func incrementSimpleCounterTotal() {
    simpleCounterTotal.Inc()
}

func setCityTemperature(city string, t float64) {
    temperature.WithLabelValues(city).Set(t)
}

func main() {
    incrementSimpleCounterTotal()
    setCityTemperature("berlin", 30)

    http.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
    http.ListenAndServe(":8000", nil)
}
package main

import (
    "testing"

    dto "github.com/prometheus/client_model/go"
)

var (
    metric dto.Metric
)

func Test_IncrementSimpleCounterTotal(t *testing.T) {
    incrementSimpleCounterTotal()

    err := simpleCounterTotal.Write(&metric)
    if err != nil {
        t.Fail()
    }

    if metric.GetCounter().GetValue() != 1.0 {
        t.Fail()
    }

    metric.Reset()
}

func Test_Temperature(t *testing.T) {
    setCityTemperature("berlin", 30)

    err := temperature.WithLabelValues("berlin").Write(&metric)
    if err != nil {
        t.Fail()
    }

    if metric.GetGauge().GetValue() != 30 {
        t.Fail()
    }

    metric.Reset()
}