timings.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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. "encoding/json"
  16. "fmt"
  17. "sync"
  18. "time"
  19. syncX "git.nspix.com/golang/micro/sync"
  20. )
  21. // Timings is meant to tracks timing data
  22. // by named categories as well as histograms.
  23. type Timings struct {
  24. totalCount syncX.AtomicInt64
  25. totalTime syncX.AtomicInt64
  26. mu sync.RWMutex
  27. histograms map[string]*Histogram
  28. name string
  29. help string
  30. label string
  31. labelCombined bool
  32. }
  33. // NewTimings creates a new Timings object, and publishes it if name is set.
  34. // categories is an optional list of categories to initialize to 0.
  35. // Categories that aren't initialized will be missing from the map until the
  36. // first time they are updated.
  37. func NewTimings(name, help, label string, categories ...string) *Timings {
  38. t := &Timings{
  39. histograms: make(map[string]*Histogram),
  40. name: name,
  41. help: help,
  42. label: label,
  43. labelCombined: IsDimensionCombined(label),
  44. }
  45. for _, cat := range categories {
  46. t.histograms[cat] = NewGenericHistogram("", "", bucketCutoffs, bucketLabels, "Count", "Time")
  47. }
  48. if name != "" {
  49. publish(name, t)
  50. }
  51. return t
  52. }
  53. // Reset will clear histograms: used during testing
  54. func (t *Timings) Reset() {
  55. t.mu.RLock()
  56. t.histograms = make(map[string]*Histogram)
  57. t.mu.RUnlock()
  58. }
  59. // Add will add a new value to the named histogram.
  60. func (t *Timings) Add(name string, elapsed time.Duration) {
  61. if t.labelCombined {
  62. name = StatsAllStr
  63. }
  64. // Get existing Histogram.
  65. t.mu.RLock()
  66. hist, ok := t.histograms[name]
  67. t.mu.RUnlock()
  68. // Create Histogram if it does not exist.
  69. if !ok {
  70. t.mu.Lock()
  71. hist, ok = t.histograms[name]
  72. if !ok {
  73. hist = NewGenericHistogram("", "", bucketCutoffs, bucketLabels, "Count", "Time")
  74. t.histograms[name] = hist
  75. }
  76. t.mu.Unlock()
  77. }
  78. if defaultStatsdHook.timerHook != nil && t.name != "" {
  79. defaultStatsdHook.timerHook(t.name, name, elapsed.Milliseconds(), t)
  80. }
  81. elapsedNs := int64(elapsed)
  82. hist.Add(elapsedNs)
  83. t.totalCount.Add(1)
  84. t.totalTime.Add(elapsedNs)
  85. }
  86. // Record is a convenience function that records completion
  87. // timing data based on the provided start time of an event.
  88. func (t *Timings) Record(name string, startTime time.Time) {
  89. if t.labelCombined {
  90. name = StatsAllStr
  91. }
  92. t.Add(name, time.Since(startTime))
  93. }
  94. // String is for expvar.
  95. func (t *Timings) String() string {
  96. t.mu.RLock()
  97. defer t.mu.RUnlock()
  98. tm := struct {
  99. TotalCount int64
  100. TotalTime int64
  101. Histograms map[string]*Histogram
  102. }{
  103. t.totalCount.Get(),
  104. t.totalTime.Get(),
  105. t.histograms,
  106. }
  107. data, err := json.Marshal(tm)
  108. if err != nil {
  109. data, _ = json.Marshal(err.Error())
  110. }
  111. return string(data)
  112. }
  113. // Histograms returns a map pointing at the histograms.
  114. func (t *Timings) Histograms() (h map[string]*Histogram) {
  115. t.mu.RLock()
  116. defer t.mu.RUnlock()
  117. h = make(map[string]*Histogram, len(t.histograms))
  118. for k, v := range t.histograms {
  119. h[k] = v
  120. }
  121. return
  122. }
  123. // Count returns the total count for all values.
  124. func (t *Timings) Count() int64 {
  125. return t.totalCount.Get()
  126. }
  127. // Time returns the total time elapsed for all values.
  128. func (t *Timings) Time() int64 {
  129. return t.totalTime.Get()
  130. }
  131. // Counts returns the total count for each value.
  132. func (t *Timings) Counts() map[string]int64 {
  133. t.mu.RLock()
  134. defer t.mu.RUnlock()
  135. counts := make(map[string]int64, len(t.histograms)+1)
  136. for k, v := range t.histograms {
  137. counts[k] = v.Count()
  138. }
  139. counts["All"] = t.totalCount.Get()
  140. return counts
  141. }
  142. // Cutoffs returns the cutoffs used in the component histograms.
  143. // Do not change the returned slice.
  144. func (t *Timings) Cutoffs() []int64 {
  145. return bucketCutoffs
  146. }
  147. // Help returns the help string.
  148. func (t *Timings) Help() string {
  149. return t.help
  150. }
  151. // Label returns the label name.
  152. func (t *Timings) Label() string {
  153. return t.label
  154. }
  155. var bucketCutoffs = []int64{5e5, 1e6, 5e6, 1e7, 5e7, 1e8, 5e8, 1e9, 5e9, 1e10}
  156. var bucketLabels []string
  157. func init() {
  158. bucketLabels = make([]string, len(bucketCutoffs)+1)
  159. for i, v := range bucketCutoffs {
  160. bucketLabels[i] = fmt.Sprintf("%d", v)
  161. }
  162. bucketLabels[len(bucketLabels)-1] = "inf"
  163. }
  164. // MultiTimings is meant to tracks timing data by categories as well
  165. // as histograms. The names of the categories are compound names made
  166. // with joining multiple strings with '.'.
  167. type MultiTimings struct {
  168. Timings
  169. labels []string
  170. combinedLabels []bool
  171. }
  172. // NewMultiTimings creates a new MultiTimings object.
  173. func NewMultiTimings(name string, help string, labels []string) *MultiTimings {
  174. combinedLabels := make([]bool, len(labels))
  175. for i, label := range labels {
  176. combinedLabels[i] = IsDimensionCombined(label)
  177. }
  178. t := &MultiTimings{
  179. Timings: Timings{
  180. histograms: make(map[string]*Histogram),
  181. name: name,
  182. help: help,
  183. label: safeJoinLabels(labels, combinedLabels),
  184. },
  185. labels: labels,
  186. combinedLabels: combinedLabels,
  187. }
  188. if name != "" {
  189. publish(name, t)
  190. }
  191. return t
  192. }
  193. // Labels returns descriptions of the parts of each compound category name.
  194. func (mt *MultiTimings) Labels() []string {
  195. return mt.labels
  196. }
  197. // Add will add a new value to the named histogram.
  198. func (mt *MultiTimings) Add(names []string, elapsed time.Duration) {
  199. if len(names) != len(mt.labels) {
  200. panic("MultiTimings: wrong number of values in Add")
  201. }
  202. mt.Timings.Add(safeJoinLabels(names, mt.combinedLabels), elapsed)
  203. }
  204. // Record is a convenience function that records completion
  205. // timing data based on the provided start time of an event.
  206. func (mt *MultiTimings) Record(names []string, startTime time.Time) {
  207. if len(names) != len(mt.labels) {
  208. panic("MultiTimings: wrong number of values in Record")
  209. }
  210. mt.Timings.Record(safeJoinLabels(names, mt.combinedLabels), startTime)
  211. }
  212. // Cutoffs returns the cutoffs used in the component histograms.
  213. // Do not change the returned slice.
  214. func (mt *MultiTimings) Cutoffs() []int64 {
  215. return bucketCutoffs
  216. }