metrics: do not export MultiLabelMap with no variables

Avoid exporting HELP and TYPE of metrics that don't yet have any
variables.

Updates tailscale/corp#22075

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
This commit is contained in:
Anton Tolchanov 2024-10-26 18:15:46 +01:00
parent fd77965f23
commit 160ba1f82c
2 changed files with 23 additions and 16 deletions

View File

@ -104,6 +104,15 @@ func (v *MultiLabelMap[T]) String() string {
// WritePrometheus writes v to w in Prometheus exposition format.
// The name argument is the metric name.
func (v *MultiLabelMap[T]) WritePrometheus(w io.Writer, name string) {
v.mu.RLock()
defer v.mu.RUnlock()
if len(v.sorted) == 0 {
// Do not print TYPE and HELP if we don't actually have any variables
// in the map.
return
}
if v.Type != "" {
io.WriteString(w, "# TYPE ")
io.WriteString(w, name)
@ -118,8 +127,6 @@ func (v *MultiLabelMap[T]) WritePrometheus(w io.Writer, name string) {
io.WriteString(w, v.Help)
io.WriteString(w, "\n")
}
v.mu.RLock()
defer v.mu.RUnlock()
for _, kv := range v.sorted {
io.WriteString(w, name)

View File

@ -17,6 +17,15 @@ type L2 struct {
Bar string `prom:"bar"`
}
func checkRendered[T comparable](t *testing.T, m *MultiLabelMap[T], name, wantRendered string) {
t.Helper()
var buf bytes.Buffer
m.WritePrometheus(&buf, name)
if got := buf.String(); got != wantRendered {
t.Errorf("prometheus output = %q; want %q", got, wantRendered)
}
}
func TestMultilabelMap(t *testing.T) {
m := new(MultiLabelMap[L2])
m.Add(L2{"a", "b"}, 2)
@ -45,19 +54,14 @@ func TestMultilabelMap(t *testing.T) {
t.Errorf("got %q; want %q", g, w)
}
var buf bytes.Buffer
m.WritePrometheus(&buf, "metricname")
const want = `metricname{foo="a",bar="a"} 1
checkRendered(t, m, "metricname", `metricname{foo="a",bar="a"} 1
metricname{foo="a",bar="b"} 2
metricname{foo="b",bar="b"} 3
metricname{foo="b",bar="c"} 4
metricname{foo="sf",bar="sf"} 5.5
metricname{foo="sfunc",bar="sfunc"} 3
metricname{foo="si",bar="si"} 5
`
if got := buf.String(); got != want {
t.Errorf("promtheus output = %q; want %q", got, want)
}
`)
m.Delete(L2{"b", "b"})
@ -95,16 +99,12 @@ type LabelTypes struct {
m := new(MultiLabelMap[LabelTypes])
m.Type = "counter"
m.Help = "some good stuff"
checkRendered(t, m, "metricname", "")
m.Add(LabelTypes{"a", true, -1, 2}, 3)
var buf bytes.Buffer
m.WritePrometheus(&buf, "metricname")
const want = `# TYPE metricname counter
checkRendered(t, m, "metricname", `# TYPE metricname counter
# HELP metricname some good stuff
metricname{s="a",b="true",i="-1",u="2"} 3
`
if got := buf.String(); got != want {
t.Errorf("got %q; want %q", got, want)
}
`)
writeAllocs := testing.AllocsPerRun(1000, func() {
m.WritePrometheus(io.Discard, "test")