vitess.io/vitess@v0.16.2/go/stats/timings.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package stats
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	"vitess.io/vitess/go/sync2"
    26  )
    27  
    28  // Timings is meant to tracks timing data
    29  // by named categories as well as histograms.
    30  type Timings struct {
    31  	totalCount sync2.AtomicInt64
    32  	totalTime  sync2.AtomicInt64
    33  
    34  	mu         sync.RWMutex
    35  	histograms map[string]*Histogram
    36  
    37  	name          string
    38  	help          string
    39  	label         string
    40  	labelCombined bool
    41  }
    42  
    43  // NewTimings creates a new Timings object, and publishes it if name is set.
    44  // categories is an optional list of categories to initialize to 0.
    45  // Categories that aren't initialized will be missing from the map until the
    46  // first time they are updated.
    47  func NewTimings(name, help, label string, categories ...string) *Timings {
    48  	t := &Timings{
    49  		histograms:    make(map[string]*Histogram),
    50  		name:          name,
    51  		help:          help,
    52  		label:         label,
    53  		labelCombined: IsDimensionCombined(label),
    54  	}
    55  	for _, cat := range categories {
    56  		t.histograms[cat] = NewGenericHistogram("", "", bucketCutoffs, bucketLabels, "Count", "Time")
    57  	}
    58  	if name != "" {
    59  		publish(name, t)
    60  	}
    61  
    62  	return t
    63  }
    64  
    65  // Reset will clear histograms: used during testing
    66  func (t *Timings) Reset() {
    67  	t.mu.RLock()
    68  	t.histograms = make(map[string]*Histogram)
    69  	t.mu.RUnlock()
    70  }
    71  
    72  // Add will add a new value to the named histogram.
    73  func (t *Timings) Add(name string, elapsed time.Duration) {
    74  	if t.labelCombined {
    75  		name = StatsAllStr
    76  	}
    77  	// Get existing Histogram.
    78  	t.mu.RLock()
    79  	hist, ok := t.histograms[name]
    80  	t.mu.RUnlock()
    81  
    82  	// Create Histogram if it does not exist.
    83  	if !ok {
    84  		t.mu.Lock()
    85  		hist, ok = t.histograms[name]
    86  		if !ok {
    87  			hist = NewGenericHistogram("", "", bucketCutoffs, bucketLabels, "Count", "Time")
    88  			t.histograms[name] = hist
    89  		}
    90  		t.mu.Unlock()
    91  	}
    92  	if defaultStatsdHook.timerHook != nil && t.name != "" {
    93  		defaultStatsdHook.timerHook(t.name, name, elapsed.Milliseconds(), t)
    94  	}
    95  
    96  	elapsedNs := int64(elapsed)
    97  	hist.Add(elapsedNs)
    98  	t.totalCount.Add(1)
    99  	t.totalTime.Add(elapsedNs)
   100  }
   101  
   102  // Record is a convenience function that records completion
   103  // timing data based on the provided start time of an event.
   104  func (t *Timings) Record(name string, startTime time.Time) {
   105  	if t.labelCombined {
   106  		name = StatsAllStr
   107  	}
   108  	t.Add(name, time.Since(startTime))
   109  }
   110  
   111  // String is for expvar.
   112  func (t *Timings) String() string {
   113  	t.mu.RLock()
   114  	defer t.mu.RUnlock()
   115  
   116  	tm := struct {
   117  		TotalCount int64
   118  		TotalTime  int64
   119  		Histograms map[string]*Histogram
   120  	}{
   121  		t.totalCount.Get(),
   122  		t.totalTime.Get(),
   123  		t.histograms,
   124  	}
   125  
   126  	data, err := json.Marshal(tm)
   127  	if err != nil {
   128  		data, _ = json.Marshal(err.Error())
   129  	}
   130  	return string(data)
   131  }
   132  
   133  // Histograms returns a map pointing at the histograms.
   134  func (t *Timings) Histograms() (h map[string]*Histogram) {
   135  	t.mu.RLock()
   136  	defer t.mu.RUnlock()
   137  	h = make(map[string]*Histogram, len(t.histograms))
   138  	for k, v := range t.histograms {
   139  		h[k] = v
   140  	}
   141  	return
   142  }
   143  
   144  // Count returns the total count for all values.
   145  func (t *Timings) Count() int64 {
   146  	return t.totalCount.Get()
   147  }
   148  
   149  // Time returns the total time elapsed for all values.
   150  func (t *Timings) Time() int64 {
   151  	return t.totalTime.Get()
   152  }
   153  
   154  // Counts returns the total count for each value.
   155  func (t *Timings) Counts() map[string]int64 {
   156  	t.mu.RLock()
   157  	defer t.mu.RUnlock()
   158  
   159  	counts := make(map[string]int64, len(t.histograms)+1)
   160  	for k, v := range t.histograms {
   161  		counts[k] = v.Count()
   162  	}
   163  	counts["All"] = t.totalCount.Get()
   164  	return counts
   165  }
   166  
   167  // Cutoffs returns the cutoffs used in the component histograms.
   168  // Do not change the returned slice.
   169  func (t *Timings) Cutoffs() []int64 {
   170  	return bucketCutoffs
   171  }
   172  
   173  // Help returns the help string.
   174  func (t *Timings) Help() string {
   175  	return t.help
   176  }
   177  
   178  // Label returns the label name.
   179  func (t *Timings) Label() string {
   180  	return t.label
   181  }
   182  
   183  var bucketCutoffs = []int64{5e5, 1e6, 5e6, 1e7, 5e7, 1e8, 5e8, 1e9, 5e9, 1e10}
   184  
   185  var bucketLabels []string
   186  
   187  func init() {
   188  	bucketLabels = make([]string, len(bucketCutoffs)+1)
   189  	for i, v := range bucketCutoffs {
   190  		bucketLabels[i] = fmt.Sprintf("%d", v)
   191  	}
   192  	bucketLabels[len(bucketLabels)-1] = "inf"
   193  }
   194  
   195  // MultiTimings is meant to tracks timing data by categories as well
   196  // as histograms. The names of the categories are compound names made
   197  // with joining multiple strings with '.'.
   198  type MultiTimings struct {
   199  	Timings
   200  	labels         []string
   201  	combinedLabels []bool
   202  }
   203  
   204  // NewMultiTimings creates a new MultiTimings object.
   205  func NewMultiTimings(name string, help string, labels []string) *MultiTimings {
   206  	combinedLabels := make([]bool, len(labels))
   207  	for i, label := range labels {
   208  		combinedLabels[i] = IsDimensionCombined(label)
   209  	}
   210  	t := &MultiTimings{
   211  		Timings: Timings{
   212  			histograms: make(map[string]*Histogram),
   213  			name:       name,
   214  			help:       help,
   215  			label:      safeJoinLabels(labels, combinedLabels),
   216  		},
   217  		labels:         labels,
   218  		combinedLabels: combinedLabels,
   219  	}
   220  	if name != "" {
   221  		publish(name, t)
   222  	}
   223  
   224  	return t
   225  }
   226  
   227  // Labels returns descriptions of the parts of each compound category name.
   228  func (mt *MultiTimings) Labels() []string {
   229  	return mt.labels
   230  }
   231  
   232  // Add will add a new value to the named histogram.
   233  func (mt *MultiTimings) Add(names []string, elapsed time.Duration) {
   234  	if len(names) != len(mt.labels) {
   235  		panic("MultiTimings: wrong number of values in Add")
   236  	}
   237  	mt.Timings.Add(safeJoinLabels(names, mt.combinedLabels), elapsed)
   238  }
   239  
   240  // Record is a convenience function that records completion
   241  // timing data based on the provided start time of an event.
   242  func (mt *MultiTimings) Record(names []string, startTime time.Time) {
   243  	if len(names) != len(mt.labels) {
   244  		panic("MultiTimings: wrong number of values in Record")
   245  	}
   246  	mt.Timings.Record(safeJoinLabels(names, mt.combinedLabels), startTime)
   247  }
   248  
   249  // Cutoffs returns the cutoffs used in the component histograms.
   250  // Do not change the returned slice.
   251  func (mt *MultiTimings) Cutoffs() []int64 {
   252  	return bucketCutoffs
   253  }