multilabelmap.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package metrics
  4. import (
  5. "expvar"
  6. "fmt"
  7. "io"
  8. "reflect"
  9. "sort"
  10. "strings"
  11. "sync"
  12. )
  13. // MultiLabelMap is a struct-value-to-Var map variable that satisfies the
  14. // [expvar.Var] interface but also allows for multiple Prometheus labels to be
  15. // associated with each value.
  16. //
  17. // T must be a struct type with scalar fields. The struct field names
  18. // (lowercased) are used as the labels, unless a "prom" struct tag is present.
  19. // The struct fields must all be strings, and the string values must be valid
  20. // Prometheus label values without requiring quoting.
  21. type MultiLabelMap[T comparable] struct {
  22. Type string // optional Prometheus type ("counter", "gauge")
  23. Help string // optional Prometheus help string
  24. m sync.Map // map[T]expvar.Var
  25. mu sync.RWMutex
  26. sorted []labelsAndValue[T] // by labels string, to match expvar.Map + for aesthetics in output
  27. }
  28. // NewMultiLabelMap creates and publishes (via expvar.Publish) a new
  29. // MultiLabelMap[T] variable with the given name and returns it.
  30. func NewMultiLabelMap[T comparable](name string, promType, helpText string) *MultiLabelMap[T] {
  31. m := &MultiLabelMap[T]{
  32. Type: promType,
  33. Help: helpText,
  34. }
  35. var zero T
  36. _ = LabelString(zero) // panic early if T is invalid
  37. expvar.Publish(name, m)
  38. return m
  39. }
  40. type labelsAndValue[T comparable] struct {
  41. key T
  42. labels string // Prometheus-formatted {label="value",label="value"} string
  43. val expvar.Var
  44. }
  45. // LabelString returns a Prometheus-formatted label string for the given key.
  46. // k must be a struct type with scalar fields, as required by MultiLabelMap,
  47. // if k is not a struct, it will panic.
  48. func LabelString(k any) string {
  49. rv := reflect.ValueOf(k)
  50. t := rv.Type()
  51. if t.Kind() != reflect.Struct {
  52. panic(fmt.Sprintf("MultiLabelMap must use keys of type struct; got %v", t))
  53. }
  54. var sb strings.Builder
  55. sb.WriteString("{")
  56. for i := range t.NumField() {
  57. if i > 0 {
  58. sb.WriteString(",")
  59. }
  60. ft := t.Field(i)
  61. label := ft.Tag.Get("prom")
  62. if label == "" {
  63. label = strings.ToLower(ft.Name)
  64. }
  65. fv := rv.Field(i)
  66. switch fv.Kind() {
  67. case reflect.String:
  68. fmt.Fprintf(&sb, "%s=%q", label, fv.String())
  69. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  70. fmt.Fprintf(&sb, "%s=\"%d\"", label, fv.Int())
  71. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  72. fmt.Fprintf(&sb, "%s=\"%d\"", label, fv.Uint())
  73. case reflect.Bool:
  74. fmt.Fprintf(&sb, "%s=\"%v\"", label, fv.Bool())
  75. default:
  76. panic(fmt.Sprintf("MultiLabelMap key field %q has unsupported type %v", ft.Name, fv.Type()))
  77. }
  78. }
  79. sb.WriteString("}")
  80. return sb.String()
  81. }
  82. // KeyValue represents a single entry in a [MultiLabelMap].
  83. type KeyValue[T comparable] struct {
  84. Key T
  85. Value expvar.Var
  86. }
  87. func (v *MultiLabelMap[T]) String() string {
  88. // NOTE: This has to be valid JSON because it's used by expvar.
  89. return `"MultiLabelMap"`
  90. }
  91. // WritePrometheus writes v to w in Prometheus exposition format.
  92. // The name argument is the metric name.
  93. func (v *MultiLabelMap[T]) WritePrometheus(w io.Writer, name string) {
  94. if v.Type != "" {
  95. io.WriteString(w, "# TYPE ")
  96. io.WriteString(w, name)
  97. io.WriteString(w, " ")
  98. io.WriteString(w, v.Type)
  99. io.WriteString(w, "\n")
  100. }
  101. if v.Help != "" {
  102. io.WriteString(w, "# HELP ")
  103. io.WriteString(w, name)
  104. io.WriteString(w, " ")
  105. io.WriteString(w, v.Help)
  106. io.WriteString(w, "\n")
  107. }
  108. v.mu.RLock()
  109. defer v.mu.RUnlock()
  110. for _, kv := range v.sorted {
  111. io.WriteString(w, name)
  112. io.WriteString(w, kv.labels)
  113. switch v := kv.val.(type) {
  114. case *expvar.Int:
  115. fmt.Fprintf(w, " %d\n", v.Value())
  116. case *expvar.Float:
  117. fmt.Fprintf(w, " %v\n", v.Value())
  118. default:
  119. fmt.Fprintf(w, " %s\n", kv.val)
  120. }
  121. }
  122. }
  123. // Init removes all keys from the map.
  124. //
  125. // Think of it as "Reset", but it's named Init to match expvar.Map.Init.
  126. func (v *MultiLabelMap[T]) Init() *MultiLabelMap[T] {
  127. v.mu.Lock()
  128. defer v.mu.Unlock()
  129. v.sorted = nil
  130. v.m.Range(func(k, _ any) bool {
  131. v.m.Delete(k)
  132. return true
  133. })
  134. return v
  135. }
  136. // addKeyLocked updates the sorted list of keys in v.keys.
  137. //
  138. // v.mu must be held.
  139. func (v *MultiLabelMap[T]) addKeyLocked(key T, val expvar.Var) {
  140. ls := LabelString(key)
  141. ent := labelsAndValue[T]{key, ls, val}
  142. // Using insertion sort to place key into the already-sorted v.keys.
  143. i := sort.Search(len(v.sorted), func(i int) bool {
  144. return v.sorted[i].labels >= ls
  145. })
  146. if i >= len(v.sorted) {
  147. v.sorted = append(v.sorted, ent)
  148. } else if v.sorted[i].key == key {
  149. v.sorted[i].val = val
  150. } else {
  151. var zero labelsAndValue[T]
  152. v.sorted = append(v.sorted, zero)
  153. copy(v.sorted[i+1:], v.sorted[i:])
  154. v.sorted[i] = ent
  155. }
  156. }
  157. // Get returns the expvar for the given key, or nil if it doesn't exist.
  158. func (v *MultiLabelMap[T]) Get(key T) expvar.Var {
  159. i, _ := v.m.Load(key)
  160. av, _ := i.(expvar.Var)
  161. return av
  162. }
  163. func newInt() expvar.Var { return new(expvar.Int) }
  164. func newFloat() expvar.Var { return new(expvar.Float) }
  165. // getOrFill returns the expvar.Var for the given key, atomically creating it
  166. // once (for all callers) with fill if it doesn't exist.
  167. func (v *MultiLabelMap[T]) getOrFill(key T, fill func() expvar.Var) expvar.Var {
  168. if v := v.Get(key); v != nil {
  169. return v
  170. }
  171. v.mu.Lock()
  172. defer v.mu.Unlock()
  173. if v := v.Get(key); v != nil {
  174. return v
  175. }
  176. nv := fill()
  177. v.addKeyLocked(key, nv)
  178. v.m.Store(key, nv)
  179. return nv
  180. }
  181. // Set sets key to val.
  182. //
  183. // This is not optimized for highly concurrent usage; it's presumed to only be
  184. // used rarely, at startup.
  185. func (v *MultiLabelMap[T]) Set(key T, val expvar.Var) {
  186. v.mu.Lock()
  187. defer v.mu.Unlock()
  188. v.addKeyLocked(key, val)
  189. v.m.Store(key, val)
  190. }
  191. // SetInt sets val to the *[expvar.Int] value stored under the given map key,
  192. // creating it if it doesn't exist yet.
  193. // It does nothing if key exists but is of the wrong type.
  194. func (v *MultiLabelMap[T]) SetInt(key T, val int64) {
  195. // Set to Int; ignore otherwise.
  196. if iv, ok := v.getOrFill(key, newInt).(*expvar.Int); ok {
  197. iv.Set(val)
  198. }
  199. }
  200. // SetFloat sets val to the *[expvar.Float] value stored under the given map key,
  201. // creating it if it doesn't exist yet.
  202. // It does nothing if key exists but is of the wrong type.
  203. func (v *MultiLabelMap[T]) SetFloat(key T, val float64) {
  204. // Set to Float; ignore otherwise.
  205. if iv, ok := v.getOrFill(key, newFloat).(*expvar.Float); ok {
  206. iv.Set(val)
  207. }
  208. }
  209. // Add adds delta to the *[expvar.Int] value stored under the given map key,
  210. // creating it if it doesn't exist yet.
  211. // It does nothing if key exists but is of the wrong type.
  212. func (v *MultiLabelMap[T]) Add(key T, delta int64) {
  213. // Add to Int; ignore otherwise.
  214. if iv, ok := v.getOrFill(key, newInt).(*expvar.Int); ok {
  215. iv.Add(delta)
  216. }
  217. }
  218. // Add adds delta to the *[expvar.Float] value stored under the given map key,
  219. // creating it if it doesn't exist yet.
  220. // It does nothing if key exists but is of the wrong type.
  221. func (v *MultiLabelMap[T]) AddFloat(key T, delta float64) {
  222. // Add to Float; ignore otherwise.
  223. if iv, ok := v.getOrFill(key, newFloat).(*expvar.Float); ok {
  224. iv.Add(delta)
  225. }
  226. }
  227. // Delete deletes the given key from the map.
  228. //
  229. // This is not optimized for highly concurrent usage; it's presumed to only be
  230. // used rarely, at startup.
  231. func (v *MultiLabelMap[T]) Delete(key T) {
  232. ls := LabelString(key)
  233. v.mu.Lock()
  234. defer v.mu.Unlock()
  235. // Using insertion sort to place key into the already-sorted v.keys.
  236. i := sort.Search(len(v.sorted), func(i int) bool {
  237. return v.sorted[i].labels >= ls
  238. })
  239. if i < len(v.sorted) && v.sorted[i].key == key {
  240. v.sorted = append(v.sorted[:i], v.sorted[i+1:]...)
  241. v.m.Delete(key)
  242. }
  243. }
  244. // Do calls f for each entry in the map.
  245. // The map is locked during the iteration,
  246. // but existing entries may be concurrently updated.
  247. func (v *MultiLabelMap[T]) Do(f func(KeyValue[T])) {
  248. v.mu.RLock()
  249. defer v.mu.RUnlock()
  250. for _, e := range v.sorted {
  251. f(KeyValue[T]{e.key, e.val})
  252. }
  253. }
  254. // ResetAllForTest resets all values for metrics to zero.
  255. // Should only be used in tests.
  256. func (v *MultiLabelMap[T]) ResetAllForTest() {
  257. v.Do(func(kv KeyValue[T]) {
  258. switch v := kv.Value.(type) {
  259. case *expvar.Int:
  260. v.Set(0)
  261. case *expvar.Float:
  262. v.Set(0)
  263. }
  264. })
  265. }