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