vitess.io/vitess@v0.16.2/go/stats/histogram.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  	"bytes"
    21  	"fmt"
    22  
    23  	"vitess.io/vitess/go/sync2"
    24  )
    25  
    26  // Histogram tracks counts and totals while
    27  // splitting the counts under different buckets
    28  // using specified cutoffs.
    29  type Histogram struct {
    30  	name       string
    31  	help       string
    32  	cutoffs    []int64
    33  	labels     []string
    34  	countLabel string
    35  	totalLabel string
    36  	hook       func(int64)
    37  
    38  	buckets []sync2.AtomicInt64
    39  	total   sync2.AtomicInt64
    40  }
    41  
    42  // NewHistogram creates a histogram with auto-generated labels
    43  // based on the cutoffs. The buckets are categorized using the
    44  // following criterion: cutoff[i-1] < value <= cutoff[i]. Anything
    45  // higher than the highest cutoff is labeled as "inf".
    46  func NewHistogram(name, help string, cutoffs []int64) *Histogram {
    47  	labels := make([]string, len(cutoffs)+1)
    48  	for i, v := range cutoffs {
    49  		labels[i] = fmt.Sprintf("%d", v)
    50  	}
    51  	labels[len(labels)-1] = "inf"
    52  	return NewGenericHistogram(name, help, cutoffs, labels, "Count", "Total")
    53  }
    54  
    55  // NewGenericHistogram creates a histogram where all the labels are
    56  // supplied by the caller. The number of labels has to be one more than
    57  // the number of cutoffs because the last label captures everything that
    58  // exceeds the highest cutoff.
    59  func NewGenericHistogram(name, help string, cutoffs []int64, labels []string, countLabel, totalLabel string) *Histogram {
    60  	if len(cutoffs) != len(labels)-1 {
    61  		panic("mismatched cutoff and label lengths")
    62  	}
    63  	h := &Histogram{
    64  		name:       name,
    65  		help:       help,
    66  		cutoffs:    cutoffs,
    67  		labels:     labels,
    68  		countLabel: countLabel,
    69  		totalLabel: totalLabel,
    70  		buckets:    make([]sync2.AtomicInt64, len(labels)),
    71  	}
    72  	if name != "" {
    73  		publish(name, h)
    74  	}
    75  	return h
    76  }
    77  
    78  // Add adds a new measurement to the Histogram.
    79  func (h *Histogram) Add(value int64) {
    80  	for i := range h.labels {
    81  		if i == len(h.labels)-1 || value <= h.cutoffs[i] {
    82  			h.buckets[i].Add(1)
    83  			h.total.Add(value)
    84  			break
    85  		}
    86  	}
    87  	if h.hook != nil {
    88  		h.hook(value)
    89  	}
    90  	if defaultStatsdHook.histogramHook != nil && h.name != "" {
    91  		defaultStatsdHook.histogramHook(h.name, value)
    92  	}
    93  }
    94  
    95  // String returns a string representation of the Histogram.
    96  // Note that sum of all buckets may not be equal to the total temporarily,
    97  // because Add() increments bucket and total with two atomic operations.
    98  func (h *Histogram) String() string {
    99  	b, _ := h.MarshalJSON()
   100  	return string(b)
   101  }
   102  
   103  // MarshalJSON returns a JSON representation of the Histogram.
   104  // Note that sum of all buckets may not be equal to the total temporarily,
   105  // because Add() increments bucket and total with two atomic operations.
   106  func (h *Histogram) MarshalJSON() ([]byte, error) {
   107  	b := bytes.NewBuffer(make([]byte, 0, 4096))
   108  	fmt.Fprintf(b, "{")
   109  	totalCount := int64(0)
   110  	for i, label := range h.labels {
   111  		count := h.buckets[i].Get()
   112  		totalCount += count
   113  		fmt.Fprintf(b, "\"%v\": %v, ", label, count)
   114  	}
   115  	fmt.Fprintf(b, "\"%s\": %v, ", h.countLabel, totalCount)
   116  	fmt.Fprintf(b, "\"%s\": %v", h.totalLabel, h.total.Get())
   117  	fmt.Fprintf(b, "}")
   118  	return b.Bytes(), nil
   119  }
   120  
   121  // Counts returns a map from labels to the current count in the Histogram for that label.
   122  func (h *Histogram) Counts() map[string]int64 {
   123  	counts := make(map[string]int64, len(h.labels))
   124  	for i, label := range h.labels {
   125  		counts[label] = h.buckets[i].Get()
   126  	}
   127  	return counts
   128  }
   129  
   130  // CountLabel returns the count label that was set when this Histogram was created.
   131  func (h *Histogram) CountLabel() string {
   132  	return h.countLabel
   133  }
   134  
   135  // Count returns the number of times Add has been called.
   136  func (h *Histogram) Count() (count int64) {
   137  	for i := range h.buckets {
   138  		count += h.buckets[i].Get()
   139  	}
   140  	return
   141  }
   142  
   143  // TotalLabel returns the total label that was set when this Histogram was created.
   144  func (h *Histogram) TotalLabel() string {
   145  	return h.totalLabel
   146  }
   147  
   148  // Total returns the sum of all values that have been added to this Histogram.
   149  func (h *Histogram) Total() (total int64) {
   150  	return h.total.Get()
   151  }
   152  
   153  // Labels returns the labels that were set when this Histogram was created.
   154  func (h *Histogram) Labels() []string {
   155  	return h.labels
   156  }
   157  
   158  // Cutoffs returns the cutoffs that were set when this Histogram was created.
   159  func (h *Histogram) Cutoffs() []int64 {
   160  	return h.cutoffs
   161  }
   162  
   163  // Buckets returns a snapshot of the current values in all buckets.
   164  func (h *Histogram) Buckets() []int64 {
   165  	buckets := make([]int64, len(h.buckets))
   166  	for i := range h.buckets {
   167  		buckets[i] = h.buckets[i].Get()
   168  	}
   169  	return buckets
   170  }
   171  
   172  // Help returns the help string.
   173  func (h *Histogram) Help() string {
   174  	return h.help
   175  }