counters.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. /*
  2. Copyright 2019 The Vitess Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package stats
  14. import (
  15. "bytes"
  16. "fmt"
  17. "strings"
  18. "sync"
  19. )
  20. // counters is similar to expvar.Map, except that it doesn't allow floats.
  21. // It is used to build CountersWithSingleLabel and GaugesWithSingleLabel.
  22. type counters struct {
  23. mu sync.Mutex
  24. counts map[string]int64
  25. help string
  26. }
  27. func (c *counters) String() string {
  28. c.mu.Lock()
  29. defer c.mu.Unlock()
  30. b := &strings.Builder{}
  31. fmt.Fprintf(b, "{")
  32. prefix := ""
  33. for k, v := range c.counts {
  34. fmt.Fprintf(b, "%s%q: %v", prefix, k, v)
  35. prefix = ", "
  36. }
  37. fmt.Fprintf(b, "}")
  38. return b.String()
  39. }
  40. func (c *counters) add(name string, value int64) {
  41. c.mu.Lock()
  42. defer c.mu.Unlock()
  43. c.counts[name] = c.counts[name] + value
  44. }
  45. func (c *counters) set(name string, value int64) {
  46. c.mu.Lock()
  47. defer c.mu.Unlock()
  48. c.counts[name] = value
  49. }
  50. func (c *counters) reset() {
  51. c.mu.Lock()
  52. defer c.mu.Unlock()
  53. c.counts = make(map[string]int64)
  54. }
  55. // ZeroAll zeroes out all values
  56. func (c *counters) ZeroAll() {
  57. c.mu.Lock()
  58. defer c.mu.Unlock()
  59. for k := range c.counts {
  60. c.counts[k] = 0
  61. }
  62. }
  63. // Counts returns a copy of the Counters' map.
  64. func (c *counters) Counts() map[string]int64 {
  65. c.mu.Lock()
  66. defer c.mu.Unlock()
  67. counts := make(map[string]int64, len(c.counts))
  68. for k, v := range c.counts {
  69. counts[k] = v
  70. }
  71. return counts
  72. }
  73. // Help returns the help string.
  74. func (c *counters) Help() string {
  75. return c.help
  76. }
  77. // CountersWithSingleLabel tracks multiple counter values for a single
  78. // dimension ("label").
  79. // It provides a Counts method which can be used for tracking rates.
  80. type CountersWithSingleLabel struct {
  81. counters
  82. label string
  83. labelCombined bool
  84. }
  85. // NewCountersWithSingleLabel create a new Counters instance.
  86. // If name is set, the variable gets published.
  87. // The function also accepts an optional list of tags that pre-creates them
  88. // initialized to 0.
  89. // label is a category name used to organize the tags. It is currently only
  90. // used by Prometheus, but not by the expvar package.
  91. func NewCountersWithSingleLabel(name, help, label string, tags ...string) *CountersWithSingleLabel {
  92. c := &CountersWithSingleLabel{
  93. counters: counters{
  94. counts: make(map[string]int64),
  95. help: help,
  96. },
  97. label: label,
  98. labelCombined: IsDimensionCombined(label),
  99. }
  100. if c.labelCombined {
  101. c.counts[StatsAllStr] = 0
  102. } else {
  103. for _, tag := range tags {
  104. c.counts[tag] = 0
  105. }
  106. }
  107. if name != "" {
  108. publish(name, c)
  109. }
  110. return c
  111. }
  112. // Label returns the label name.
  113. func (c *CountersWithSingleLabel) Label() string {
  114. return c.label
  115. }
  116. // Add adds a value to a named counter.
  117. func (c *CountersWithSingleLabel) Add(name string, value int64) {
  118. if c.labelCombined {
  119. name = StatsAllStr
  120. }
  121. c.counters.add(name, value)
  122. }
  123. // Reset resets the value for the name.
  124. func (c *CountersWithSingleLabel) Reset(name string) {
  125. if c.labelCombined {
  126. name = StatsAllStr
  127. }
  128. c.counters.set(name, 0)
  129. }
  130. // ResetAll clears the counters
  131. func (c *CountersWithSingleLabel) ResetAll() {
  132. c.counters.reset()
  133. }
  134. // CountersWithMultiLabels is a multidimensional counters implementation.
  135. // Internally, each tuple of dimensions ("labels") is stored as a single
  136. // label value where all label values are joined with ".".
  137. type CountersWithMultiLabels struct {
  138. counters
  139. labels []string
  140. combinedLabels []bool
  141. }
  142. // NewCountersWithMultiLabels creates a new CountersWithMultiLabels
  143. // instance, and publishes it if name is set.
  144. func NewCountersWithMultiLabels(name, help string, labels []string) *CountersWithMultiLabels {
  145. t := &CountersWithMultiLabels{
  146. counters: counters{
  147. counts: make(map[string]int64),
  148. help: help},
  149. labels: labels,
  150. combinedLabels: make([]bool, len(labels)),
  151. }
  152. for i, label := range labels {
  153. t.combinedLabels[i] = IsDimensionCombined(label)
  154. }
  155. if name != "" {
  156. publish(name, t)
  157. }
  158. return t
  159. }
  160. // Labels returns the list of labels.
  161. func (mc *CountersWithMultiLabels) Labels() []string {
  162. return mc.labels
  163. }
  164. // Add adds a value to a named counter.
  165. // len(names) must be equal to len(Labels)
  166. func (mc *CountersWithMultiLabels) Add(names []string, value int64) {
  167. if len(names) != len(mc.labels) {
  168. panic("CountersWithMultiLabels: wrong number of values in Add")
  169. }
  170. mc.counters.add(safeJoinLabels(names, mc.combinedLabels), value)
  171. }
  172. // Reset resets the value of a named counter back to 0.
  173. // len(names) must be equal to len(Labels).
  174. func (mc *CountersWithMultiLabels) Reset(names []string) {
  175. if len(names) != len(mc.labels) {
  176. panic("CountersWithMultiLabels: wrong number of values in Reset")
  177. }
  178. mc.counters.set(safeJoinLabels(names, mc.combinedLabels), 0)
  179. }
  180. // ResetAll clears the counters
  181. func (mc *CountersWithMultiLabels) ResetAll() {
  182. mc.counters.reset()
  183. }
  184. // Counts returns a copy of the Counters' map.
  185. // The key is a single string where all labels are joined by a "." e.g.
  186. // "label1.label2".
  187. func (mc *CountersWithMultiLabels) Counts() map[string]int64 {
  188. return mc.counters.Counts()
  189. }
  190. // CountersFuncWithMultiLabels is a multidimensional counters implementation
  191. // where names of categories are compound names made with joining
  192. // multiple strings with '.'. Since the map is returned by the
  193. // function, we assume it's in the right format (meaning each key is
  194. // of the form 'aaa.bbb.ccc' with as many elements as there are in
  195. // Labels).
  196. //
  197. // Note that there is no CountersFuncWithSingleLabel object. That this
  198. // because such an object would be identical to this one because these
  199. // function-based counters have no Add() or Set() method which are different
  200. // for the single vs. multiple labels cases.
  201. // If you have only a single label, pass an array with a single element.
  202. type CountersFuncWithMultiLabels struct {
  203. f func() map[string]int64
  204. help string
  205. labels []string
  206. }
  207. // Labels returns the list of labels.
  208. func (c CountersFuncWithMultiLabels) Labels() []string {
  209. return c.labels
  210. }
  211. // Help returns the help string.
  212. func (c CountersFuncWithMultiLabels) Help() string {
  213. return c.help
  214. }
  215. // NewCountersFuncWithMultiLabels creates a new CountersFuncWithMultiLabels
  216. // mapping to the provided function.
  217. func NewCountersFuncWithMultiLabels(name, help string, labels []string, f func() map[string]int64) *CountersFuncWithMultiLabels {
  218. t := &CountersFuncWithMultiLabels{
  219. f: f,
  220. help: help,
  221. labels: labels,
  222. }
  223. if name != "" {
  224. publish(name, t)
  225. }
  226. return t
  227. }
  228. // Counts returns a copy of the counters' map.
  229. func (c CountersFuncWithMultiLabels) Counts() map[string]int64 {
  230. return c.f()
  231. }
  232. // String implements the expvar.Var interface.
  233. func (c CountersFuncWithMultiLabels) String() string {
  234. m := c.f()
  235. if m == nil {
  236. return "{}"
  237. }
  238. b := bytes.NewBuffer(make([]byte, 0, 4096))
  239. fmt.Fprintf(b, "{")
  240. firstValue := true
  241. for k, v := range m {
  242. if firstValue {
  243. firstValue = false
  244. } else {
  245. fmt.Fprintf(b, ", ")
  246. }
  247. fmt.Fprintf(b, "%q: %v", k, v)
  248. }
  249. fmt.Fprintf(b, "}")
  250. return b.String()
  251. }
  252. // GaugesWithSingleLabel is similar to CountersWithSingleLabel, except its
  253. // meant to track the current value and not a cumulative count.
  254. type GaugesWithSingleLabel struct {
  255. CountersWithSingleLabel
  256. }
  257. // NewGaugesWithSingleLabel creates a new GaugesWithSingleLabel and
  258. // publishes it if the name is set.
  259. func NewGaugesWithSingleLabel(name, help, label string, tags ...string) *GaugesWithSingleLabel {
  260. g := &GaugesWithSingleLabel{
  261. CountersWithSingleLabel: CountersWithSingleLabel{
  262. counters: counters{
  263. counts: make(map[string]int64),
  264. help: help,
  265. },
  266. label: label,
  267. },
  268. }
  269. for _, tag := range tags {
  270. g.counts[tag] = 0
  271. }
  272. if name != "" {
  273. publish(name, g)
  274. }
  275. return g
  276. }
  277. // Set sets the value of a named gauge.
  278. func (g *GaugesWithSingleLabel) Set(name string, value int64) {
  279. g.counters.set(name, value)
  280. }
  281. // GaugesWithMultiLabels is a CountersWithMultiLabels implementation where
  282. // the values can go up and down.
  283. type GaugesWithMultiLabels struct {
  284. CountersWithMultiLabels
  285. }
  286. // NewGaugesWithMultiLabels creates a new GaugesWithMultiLabels instance,
  287. // and publishes it if name is set.
  288. func NewGaugesWithMultiLabels(name, help string, labels []string) *GaugesWithMultiLabels {
  289. t := &GaugesWithMultiLabels{
  290. CountersWithMultiLabels: CountersWithMultiLabels{
  291. counters: counters{
  292. counts: make(map[string]int64),
  293. help: help,
  294. },
  295. labels: labels,
  296. }}
  297. if name != "" {
  298. publish(name, t)
  299. }
  300. return t
  301. }
  302. // Set sets the value of a named counter.
  303. // len(names) must be equal to len(Labels).
  304. func (mg *GaugesWithMultiLabels) Set(names []string, value int64) {
  305. if len(names) != len(mg.CountersWithMultiLabels.labels) {
  306. panic("GaugesWithMultiLabels: wrong number of values in Set")
  307. }
  308. mg.counters.set(safeJoinLabels(names, nil), value)
  309. }
  310. // GaugesFuncWithMultiLabels is a wrapper around CountersFuncWithMultiLabels
  311. // for values that go up/down for implementations (like Prometheus) that
  312. // need to differ between Counters and Gauges.
  313. type GaugesFuncWithMultiLabels struct {
  314. CountersFuncWithMultiLabels
  315. }
  316. // NewGaugesFuncWithMultiLabels creates a new GaugesFuncWithMultiLabels
  317. // mapping to the provided function.
  318. func NewGaugesFuncWithMultiLabels(name, help string, labels []string, f func() map[string]int64) *GaugesFuncWithMultiLabels {
  319. t := &GaugesFuncWithMultiLabels{
  320. CountersFuncWithMultiLabels: CountersFuncWithMultiLabels{
  321. f: f,
  322. help: help,
  323. labels: labels,
  324. }}
  325. if name != "" {
  326. publish(name, t)
  327. }
  328. return t
  329. }