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