github.com/XiaoMi/Gaea@v1.2.5/stats/timings.go (about)

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