multilabelmap_test.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package metrics
  4. import (
  5. "bytes"
  6. "encoding/json"
  7. "expvar"
  8. "fmt"
  9. "io"
  10. "testing"
  11. )
  12. type L2 struct {
  13. Foo string `prom:"foo"`
  14. Bar string `prom:"bar"`
  15. }
  16. func TestMultilabelMap(t *testing.T) {
  17. m := new(MultiLabelMap[L2])
  18. m.Add(L2{"a", "b"}, 2)
  19. m.Add(L2{"b", "c"}, 4)
  20. m.Add(L2{"b", "b"}, 3)
  21. m.Add(L2{"a", "a"}, 1)
  22. m.SetFloat(L2{"sf", "sf"}, 3.5)
  23. m.SetFloat(L2{"sf", "sf"}, 5.5)
  24. m.Set(L2{"sfunc", "sfunc"}, expvar.Func(func() any { return 3 }))
  25. m.SetInt(L2{"si", "si"}, 3)
  26. m.SetInt(L2{"si", "si"}, 5)
  27. cur := func() string {
  28. var buf bytes.Buffer
  29. m.Do(func(kv KeyValue[L2]) {
  30. if buf.Len() > 0 {
  31. buf.WriteString(",")
  32. }
  33. fmt.Fprintf(&buf, "%s/%s=%v", kv.Key.Foo, kv.Key.Bar, kv.Value)
  34. })
  35. return buf.String()
  36. }
  37. if g, w := cur(), "a/a=1,a/b=2,b/b=3,b/c=4,sf/sf=5.5,sfunc/sfunc=3,si/si=5"; g != w {
  38. t.Errorf("got %q; want %q", g, w)
  39. }
  40. var buf bytes.Buffer
  41. m.WritePrometheus(&buf, "metricname")
  42. const want = `metricname{foo="a",bar="a"} 1
  43. metricname{foo="a",bar="b"} 2
  44. metricname{foo="b",bar="b"} 3
  45. metricname{foo="b",bar="c"} 4
  46. metricname{foo="sf",bar="sf"} 5.5
  47. metricname{foo="sfunc",bar="sfunc"} 3
  48. metricname{foo="si",bar="si"} 5
  49. `
  50. if got := buf.String(); got != want {
  51. t.Errorf("promtheus output = %q; want %q", got, want)
  52. }
  53. m.Delete(L2{"b", "b"})
  54. if g, w := cur(), "a/a=1,a/b=2,b/c=4,sf/sf=5.5,sfunc/sfunc=3,si/si=5"; g != w {
  55. t.Errorf("got %q; want %q", g, w)
  56. }
  57. allocs := testing.AllocsPerRun(1000, func() {
  58. m.Add(L2{"a", "a"}, 1)
  59. })
  60. if allocs > 0 {
  61. t.Errorf("allocs = %v; want 0", allocs)
  62. }
  63. m.Init()
  64. if g, w := cur(), ""; g != w {
  65. t.Errorf("got %q; want %q", g, w)
  66. }
  67. writeAllocs := testing.AllocsPerRun(1000, func() {
  68. m.WritePrometheus(io.Discard, "test")
  69. })
  70. if writeAllocs > 0 {
  71. t.Errorf("writeAllocs = %v; want 0", writeAllocs)
  72. }
  73. }
  74. func TestMultiLabelMapTypes(t *testing.T) {
  75. type LabelTypes struct {
  76. S string
  77. B bool
  78. I int
  79. U uint
  80. }
  81. m := new(MultiLabelMap[LabelTypes])
  82. m.Type = "counter"
  83. m.Help = "some good stuff"
  84. m.Add(LabelTypes{"a", true, -1, 2}, 3)
  85. var buf bytes.Buffer
  86. m.WritePrometheus(&buf, "metricname")
  87. const want = `# TYPE metricname counter
  88. # HELP metricname some good stuff
  89. metricname{s="a",b="true",i="-1",u="2"} 3
  90. `
  91. if got := buf.String(); got != want {
  92. t.Errorf("got %q; want %q", got, want)
  93. }
  94. writeAllocs := testing.AllocsPerRun(1000, func() {
  95. m.WritePrometheus(io.Discard, "test")
  96. })
  97. if writeAllocs > 0 {
  98. t.Errorf("writeAllocs = %v; want 0", writeAllocs)
  99. }
  100. }
  101. func BenchmarkMultiLabelWriteAllocs(b *testing.B) {
  102. b.ReportAllocs()
  103. m := new(MultiLabelMap[L2])
  104. m.Add(L2{"a", "b"}, 2)
  105. m.Add(L2{"b", "c"}, 4)
  106. m.Add(L2{"b", "b"}, 3)
  107. m.Add(L2{"a", "a"}, 1)
  108. var w io.Writer = io.Discard
  109. b.ResetTimer()
  110. for range b.N {
  111. m.WritePrometheus(w, "test")
  112. }
  113. }
  114. func TestMultiLabelMapExpvar(t *testing.T) {
  115. m := new(MultiLabelMap[L2])
  116. m.Add(L2{"a", "b"}, 2)
  117. m.Add(L2{"b", "c"}, 4)
  118. em := new(expvar.Map)
  119. em.Set("multi", m)
  120. // Ensure that the String method is valid JSON to ensure that it can be
  121. // used by expvar.
  122. encoded := []byte(em.String())
  123. if !json.Valid(encoded) {
  124. t.Fatalf("invalid JSON: %s", encoded)
  125. }
  126. t.Logf("em = %+v", em)
  127. }