blob: 2552ac99bfb2e3d3cbc21a402de734f225780c8e [file] [log] [blame]
package testutils
import (
"fmt"
"io"
"net/http/httptest"
"sort"
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/sktest"
"go.skia.org/infra/go/util"
)
// GetRecordedMetric returns the value that prometheus is reporting.
// Using this allows for unit tests to check that metrics are properly
// being calculated and sent. This approach was found to be less awkward
// than using mocks and is decently performant.
// See datahopper/bot_metrics/bots_test.go for an example use.
func GetRecordedMetric(t sktest.TestingT, metricName string, tags map[string]string) string {
req := httptest.NewRequest("GET", "/metrics", nil)
rw := httptest.NewRecorder()
promhttp.HandlerFor(prometheus.DefaultRegisterer.(*prometheus.Registry), promhttp.HandlerOpts{
ErrorLog: nil,
ErrorHandling: promhttp.PanicOnError,
DisableCompression: true,
}).ServeHTTP(rw, req)
resp := rw.Result()
defer util.Close(resp.Body)
b, err := io.ReadAll(resp.Body)
require.NoError(t, err)
// b at this point looks like:
// # HELP go_gc_duration_seconds A summary of the GC invocation durations.
// # TYPE go_gc_duration_seconds summary
// go_gc_duration_seconds{quantile="0"} 0
// go_gc_duration_seconds{quantile="0.5"} 0
// go_gc_duration_seconds{quantile="1"} 0
// go_gc_duration_seconds_sum 0
// # ...
metric := metricName + stringifyTags(tags)
for _, s := range strings.Split(string(b), "\n") {
if strings.HasPrefix(s, metric) {
split := strings.Split(s, " ")
return split[len(split)-1]
}
}
return "Could not find anything for " + metric
}
// stringifyTags takes the given tags and returns them as would match the prometheus query
// format (e.g. `{key1="value1",key2="value2"}`) or "" if the map is empty.
func stringifyTags(tags map[string]string) string {
if len(tags) == 0 {
// Metrics w/o tags are stored on a line that look like:
// go_goroutines 10
// So we don't want to append {} otherwise, nothing will match.
return ""
}
// Prometheus always puts tags/labels in order by key value
// https://github.com/prometheus/client_golang/blob/94ff84a9a6ebb5e6eb9172897c221a64df3443bc/prometheus/desc.go#L106
// We do the same for tests
keys := []string{}
for k := range tags {
keys = append(keys, k)
}
sort.Strings(keys)
labelStrings := []string{}
for _, k := range keys {
labelStrings = append(labelStrings, fmt.Sprintf(`%s="%s"`, k, tags[k]))
}
return "{" + strings.Join(labelStrings, ",") + "}"
}