prometheus CollectAndCount always returns 1

Viewed 781

I want to instrument a program and check with tests if the instrumentation had been done properly. I discovered the testutil which has a function called CollectAndCount. I expect that the function returns the same count I would see on the HTTP metric endpoint. When I increase the counter in the example below 2 times the CollectAndCount returns 1 and not as expected 2. It would be feasible to use in this case the ToFloat64 function. As I want to get the count for a histogram too the ToFloat64 is not available.

package main

import (
    "testing"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/testutil"
)

func TestMetricT(t *testing.T) {
    var Duration = prometheus.NewHistogram(prometheus.HistogramOpts{
        Name:    "duration",
        Help:    "Help",
        Buckets: prometheus.DefBuckets,
    })

    var Errors = prometheus.NewCounter(prometheus.CounterOpts{
        Name: "errors",
        Help: "Help",
    })

    prometheus.MustRegister(Duration, Errors)

    timer := prometheus.NewTimer(Duration)
    timer.ObserveDuration()
    Errors.Inc()

    timer = prometheus.NewTimer(Duration)
    timer.ObserveDuration()

    Errors.Inc()

    errors := testutil.CollectAndCount(Errors)
    if errors != 2 {
        t.Error("no 2 increments but", errors, "ToFloat64 counts", testutil.ToFloat64(Errors))
    }

    observations := testutil.CollectAndCount(Duration)
    if observations != 2 {
        t.Error("not 2 observations but", observations)
    }

}

Output is:

--- FAIL: TestMetricT (0.00s)
    so_test.go:35: no 2 increments but 1 ToFloat64 counts 2
    so_test.go:40: not 2 observations but 1
FAIL
FAIL
1 Answers

I've used slightly different metrics than your example to illustrate what is happening and I've also decided to use testify to get a more compact example. Feel free to ask if there are any questions left.


TL;DR: CollectAndCount returns the number of metrics that were collected, ToFloat64 retruns the actual value of a single metric.

package main

import (
    "testing"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/testutil"
    "github.com/stretchr/testify/assert"
)

func TestMetricT(t *testing.T) {
    assert := assert.New(t)

    var A = prometheus.NewCounter(prometheus.CounterOpts{
        Name: "A",
        Help: "Help",
    })

    var B = prometheus.NewCounter(prometheus.CounterOpts{
        Name: "B",
        Help: "Help",
    })

    // CounterVec is a Collector that bundles a set of Counters
    var C = prometheus.NewCounterVec(prometheus.CounterOpts{
        Name: "C",
        Help: "Help",
    }, []string{"subname"},
    )

    prometheus.MustRegister(A, B, C)

    // After the registration, simple metrics like A and B can
    // be collected with the CollectAndCount function.
    // The CollectAndCount function seems to count the number of
    // metrics which were collected, not their value.
    assert.Equal(1, testutil.CollectAndCount(A))
    assert.Equal(1, testutil.CollectAndCount(B))
    // Different to the "simple" metrics like the counters A and B,
    // C is a CounterVec, a collection of counters that share
    // the same description.
    // After its registration, this collection contains 0 metrics,
    // until one is added to the collection.
    assert.Equal(0, testutil.CollectAndCount(C))

    // ToFloat64 returns the actual value of the metric. After the
    // registration, the value of the counters A and B is 0.
    assert.Equal(float64(0), testutil.ToFloat64(A))
    assert.Equal(float64(0), testutil.ToFloat64(B))

    A.Inc()
    B.Inc()

    // After we incremented A and B, CollectAndCount still
    // collects exactly one metric series for A, and one for B.
    assert.Equal(1, testutil.CollectAndCount(A))
    assert.Equal(1, testutil.CollectAndCount(B))
    // But the actual values of the counters were incremented by 1.
    assert.Equal(float64(1), testutil.ToFloat64(A))
    assert.Equal(float64(1), testutil.ToFloat64(B))

    A.Inc()
    B.Inc()

    assert.Equal(1, testutil.CollectAndCount(A))
    assert.Equal(1, testutil.CollectAndCount(B))
    // Now we incremented the counters again, and ToFloat64
    // returns 2, since we've incremented each counter 2 times.
    assert.Equal(float64(2), testutil.ToFloat64(A))
    assert.Equal(float64(2), testutil.ToFloat64(B))

    A.Inc()

    assert.Equal(1, testutil.CollectAndCount(A))
    assert.Equal(1, testutil.CollectAndCount(B))
    // Only incremented A
    assert.Equal(float64(3), testutil.ToFloat64(A))
    assert.Equal(float64(2), testutil.ToFloat64(B))

    // To increment C, we have to use a label. This
    // adds a metric with the label "firstLabel" to the
    // collection C.
    C.WithLabelValues("firstLabel").Inc()
    // Now, CollectAndCount will collect the one metric which
    // is contained in the collection C.
    assert.Equal(1, testutil.CollectAndCount(C))
    // The ToFloat64 value of the metrics is as expected.
    // We have to again use a label here, since we cannot get a
    // single float value from a collection.
    assert.Equal(float64(1), testutil.ToFloat64(C.WithLabelValues("firstLabel")))

    // Let's increase the "firstLabel" metric again and add another one.
    C.WithLabelValues("firstLabel").Inc()
    C.WithLabelValues("secondLabel").Inc()

    // CollectAndCount now collects 2 metrics from the collection C.
    assert.Equal(2, testutil.CollectAndCount(C))
    assert.Equal(float64(2), testutil.ToFloat64(C.WithLabelValues("firstLabel")))
    assert.Equal(float64(1), testutil.ToFloat64(C.WithLabelValues("secondLabel")))

    C.WithLabelValues("firstLabel").Inc()
    C.WithLabelValues("secondLabel").Inc()
    C.WithLabelValues("thirdLabel").Inc()

    assert.Equal(3, testutil.CollectAndCount(C))
    assert.Equal(float64(3), testutil.ToFloat64(C.WithLabelValues("firstLabel")))
    assert.Equal(float64(2), testutil.ToFloat64(C.WithLabelValues("secondLabel")))
    assert.Equal(float64(1), testutil.ToFloat64(C.WithLabelValues("thirdLabel")))
}

Related