...

Source file src/k8s.io/component-base/metrics/prometheusextension/timing_histogram.go

Documentation: k8s.io/component-base/metrics/prometheusextension

     1  /*
     2  Copyright 2022 The Kubernetes 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 prometheusextension
    18  
    19  import (
    20  	"errors"
    21  	"time"
    22  
    23  	"github.com/prometheus/client_golang/prometheus"
    24  	dto "github.com/prometheus/client_model/go"
    25  )
    26  
    27  // GaugeOps is the part of `prometheus.Gauge` that is relevant to
    28  // instrumented code.
    29  // This factoring should be in prometheus, analogous to the way
    30  // it already factors out the Observer interface for histograms and summaries.
    31  type GaugeOps interface {
    32  	// Set is the same as Gauge.Set
    33  	Set(float64)
    34  	// Inc is the same as Gauge.inc
    35  	Inc()
    36  	// Dec is the same as Gauge.Dec
    37  	Dec()
    38  	// Add is the same as Gauge.Add
    39  	Add(float64)
    40  	// Sub is the same as Gauge.Sub
    41  	Sub(float64)
    42  
    43  	// SetToCurrentTime the same as Gauge.SetToCurrentTime
    44  	SetToCurrentTime()
    45  }
    46  
    47  // A TimingHistogram tracks how long a `float64` variable spends in
    48  // ranges defined by buckets.  Time is counted in nanoseconds.  The
    49  // histogram's sum is the integral over time (in nanoseconds, from
    50  // creation of the histogram) of the variable's value.
    51  type TimingHistogram interface {
    52  	prometheus.Metric
    53  	prometheus.Collector
    54  	GaugeOps
    55  }
    56  
    57  // TimingHistogramOpts is the parameters of the TimingHistogram constructor
    58  type TimingHistogramOpts struct {
    59  	Namespace   string
    60  	Subsystem   string
    61  	Name        string
    62  	Help        string
    63  	ConstLabels prometheus.Labels
    64  
    65  	// Buckets defines the buckets into which observations are
    66  	// accumulated. Each element in the slice is the upper
    67  	// inclusive bound of a bucket. The values must be sorted in
    68  	// strictly increasing order. There is no need to add a
    69  	// highest bucket with +Inf bound. The default value is
    70  	// prometheus.DefBuckets.
    71  	Buckets []float64
    72  
    73  	// The initial value of the variable.
    74  	InitialValue float64
    75  }
    76  
    77  // NewTimingHistogram creates a new TimingHistogram
    78  func NewTimingHistogram(opts TimingHistogramOpts) (TimingHistogram, error) {
    79  	return NewTestableTimingHistogram(time.Now, opts)
    80  }
    81  
    82  // NewTestableTimingHistogram creates a TimingHistogram that uses a mockable clock
    83  func NewTestableTimingHistogram(nowFunc func() time.Time, opts TimingHistogramOpts) (TimingHistogram, error) {
    84  	desc := prometheus.NewDesc(
    85  		prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
    86  		wrapTimingHelp(opts.Help),
    87  		nil,
    88  		opts.ConstLabels,
    89  	)
    90  	return newTimingHistogram(nowFunc, desc, opts)
    91  }
    92  
    93  func wrapTimingHelp(given string) string {
    94  	return "EXPERIMENTAL: " + given
    95  }
    96  
    97  func newTimingHistogram(nowFunc func() time.Time, desc *prometheus.Desc, opts TimingHistogramOpts, variableLabelValues ...string) (TimingHistogram, error) {
    98  	allLabelsM := prometheus.Labels{}
    99  	allLabelsS := prometheus.MakeLabelPairs(desc, variableLabelValues)
   100  	for _, pair := range allLabelsS {
   101  		if pair == nil || pair.Name == nil || pair.Value == nil {
   102  			return nil, errors.New("prometheus.MakeLabelPairs returned a nil")
   103  		}
   104  		allLabelsM[*pair.Name] = *pair.Value
   105  	}
   106  	weighted, err := newWeightedHistogram(desc, WeightedHistogramOpts{
   107  		Namespace:   opts.Namespace,
   108  		Subsystem:   opts.Subsystem,
   109  		Name:        opts.Name,
   110  		Help:        opts.Help,
   111  		ConstLabels: allLabelsM,
   112  		Buckets:     opts.Buckets,
   113  	}, variableLabelValues...)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	return &timingHistogram{
   118  		nowFunc:     nowFunc,
   119  		weighted:    weighted,
   120  		lastSetTime: nowFunc(),
   121  		value:       opts.InitialValue,
   122  	}, nil
   123  }
   124  
   125  type timingHistogram struct {
   126  	nowFunc  func() time.Time
   127  	weighted *weightedHistogram
   128  
   129  	// The following fields must only be accessed with weighted's lock held
   130  
   131  	lastSetTime time.Time // identifies when value was last set
   132  	value       float64
   133  }
   134  
   135  var _ TimingHistogram = &timingHistogram{}
   136  
   137  func (th *timingHistogram) Set(newValue float64) {
   138  	th.update(func(float64) float64 { return newValue })
   139  }
   140  
   141  func (th *timingHistogram) Inc() {
   142  	th.update(func(oldValue float64) float64 { return oldValue + 1 })
   143  }
   144  
   145  func (th *timingHistogram) Dec() {
   146  	th.update(func(oldValue float64) float64 { return oldValue - 1 })
   147  }
   148  
   149  func (th *timingHistogram) Add(delta float64) {
   150  	th.update(func(oldValue float64) float64 { return oldValue + delta })
   151  }
   152  
   153  func (th *timingHistogram) Sub(delta float64) {
   154  	th.update(func(oldValue float64) float64 { return oldValue - delta })
   155  }
   156  
   157  func (th *timingHistogram) SetToCurrentTime() {
   158  	th.update(func(oldValue float64) float64 { return th.nowFunc().Sub(time.Unix(0, 0)).Seconds() })
   159  }
   160  
   161  func (th *timingHistogram) update(updateFn func(float64) float64) {
   162  	th.weighted.lock.Lock()
   163  	defer th.weighted.lock.Unlock()
   164  	now := th.nowFunc()
   165  	delta := now.Sub(th.lastSetTime)
   166  	value := th.value
   167  	if delta > 0 {
   168  		th.weighted.observeWithWeightLocked(value, uint64(delta))
   169  		th.lastSetTime = now
   170  	}
   171  	th.value = updateFn(value)
   172  }
   173  
   174  func (th *timingHistogram) Desc() *prometheus.Desc {
   175  	return th.weighted.Desc()
   176  }
   177  
   178  func (th *timingHistogram) Write(dest *dto.Metric) error {
   179  	th.Add(0) // account for time since last update
   180  	return th.weighted.Write(dest)
   181  }
   182  
   183  func (th *timingHistogram) Describe(ch chan<- *prometheus.Desc) {
   184  	ch <- th.weighted.Desc()
   185  }
   186  
   187  func (th *timingHistogram) Collect(ch chan<- prometheus.Metric) {
   188  	ch <- th
   189  }
   190  

View as plain text