2015-02-18 21:23:34 +03:00
|
|
|
|
// Copyright 2015 The Prometheus 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 prometheus
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"math"
|
2018-09-07 17:20:30 +03:00
|
|
|
|
"runtime"
|
2015-02-19 21:50:14 +03:00
|
|
|
|
"sort"
|
2018-09-07 17:20:30 +03:00
|
|
|
|
"sync"
|
2015-02-18 21:23:34 +03:00
|
|
|
|
"sync/atomic"
|
|
|
|
|
|
2015-02-27 18:12:59 +03:00
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
|
|
|
|
|
|
dto "github.com/prometheus/client_model/go"
|
2015-02-18 21:23:34 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// A Histogram counts individual observations from an event or sample stream in
|
|
|
|
|
// configurable buckets. Similar to a summary, it also provides a sum of
|
|
|
|
|
// observations and an observation count.
|
|
|
|
|
//
|
|
|
|
|
// On the Prometheus server, quantiles can be calculated from a Histogram using
|
|
|
|
|
// the histogram_quantile function in the query language.
|
|
|
|
|
//
|
|
|
|
|
// Note that Histograms, in contrast to Summaries, can be aggregated with the
|
|
|
|
|
// Prometheus query language (see the documentation for detailed
|
2015-02-19 17:31:43 +03:00
|
|
|
|
// procedures). However, Histograms require the user to pre-define suitable
|
2015-02-18 21:23:34 +03:00
|
|
|
|
// buckets, and they are in general less accurate. The Observe method of a
|
|
|
|
|
// Histogram has a very low performance overhead in comparison with the Observe
|
|
|
|
|
// method of a Summary.
|
|
|
|
|
//
|
|
|
|
|
// To create Histogram instances, use NewHistogram.
|
|
|
|
|
type Histogram interface {
|
|
|
|
|
Metric
|
|
|
|
|
Collector
|
|
|
|
|
|
|
|
|
|
// Observe adds a single observation to the histogram.
|
|
|
|
|
Observe(float64)
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-23 14:51:32 +03:00
|
|
|
|
// bucketLabel is used for the label that defines the upper bound of a
|
|
|
|
|
// bucket of a histogram ("le" -> "less or equal").
|
|
|
|
|
const bucketLabel = "le"
|
|
|
|
|
|
2016-08-03 13:50:39 +03:00
|
|
|
|
// DefBuckets are the default Histogram buckets. The default buckets are
|
|
|
|
|
// tailored to broadly measure the response time (in seconds) of a network
|
|
|
|
|
// service. Most likely, however, you will be required to define buckets
|
|
|
|
|
// customized to your use case.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
var (
|
|
|
|
|
DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
|
2015-02-19 17:31:43 +03:00
|
|
|
|
|
|
|
|
|
errBucketLabelNotAllowed = fmt.Errorf(
|
2015-08-23 14:51:32 +03:00
|
|
|
|
"%q is not allowed as label name in histograms", bucketLabel,
|
2015-02-19 17:31:43 +03:00
|
|
|
|
)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// LinearBuckets creates 'count' buckets, each 'width' wide, where the lowest
|
2015-02-19 14:40:29 +03:00
|
|
|
|
// bucket has an upper bound of 'start'. The final +Inf bucket is not counted
|
|
|
|
|
// and not included in the returned slice. The returned slice is meant to be
|
|
|
|
|
// used for the Buckets field of HistogramOpts.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
//
|
2015-02-19 14:40:29 +03:00
|
|
|
|
// The function panics if 'count' is zero or negative.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
func LinearBuckets(start, width float64, count int) []float64 {
|
2015-02-19 14:40:29 +03:00
|
|
|
|
if count < 1 {
|
|
|
|
|
panic("LinearBuckets needs a positive count")
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
2015-02-19 14:40:29 +03:00
|
|
|
|
buckets := make([]float64, count)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
for i := range buckets {
|
|
|
|
|
buckets[i] = start
|
|
|
|
|
start += width
|
|
|
|
|
}
|
|
|
|
|
return buckets
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ExponentialBuckets creates 'count' buckets, where the lowest bucket has an
|
|
|
|
|
// upper bound of 'start' and each following bucket's upper bound is 'factor'
|
2015-02-19 14:40:29 +03:00
|
|
|
|
// times the previous bucket's upper bound. The final +Inf bucket is not counted
|
|
|
|
|
// and not included in the returned slice. The returned slice is meant to be
|
|
|
|
|
// used for the Buckets field of HistogramOpts.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
//
|
2015-02-19 14:40:29 +03:00
|
|
|
|
// The function panics if 'count' is 0 or negative, if 'start' is 0 or negative,
|
2015-02-18 21:23:34 +03:00
|
|
|
|
// or if 'factor' is less than or equal 1.
|
|
|
|
|
func ExponentialBuckets(start, factor float64, count int) []float64 {
|
2015-02-19 14:40:29 +03:00
|
|
|
|
if count < 1 {
|
|
|
|
|
panic("ExponentialBuckets needs a positive count")
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
if start <= 0 {
|
|
|
|
|
panic("ExponentialBuckets needs a positive start value")
|
|
|
|
|
}
|
|
|
|
|
if factor <= 1 {
|
|
|
|
|
panic("ExponentialBuckets needs a factor greater than 1")
|
|
|
|
|
}
|
2015-02-19 14:40:29 +03:00
|
|
|
|
buckets := make([]float64, count)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
for i := range buckets {
|
|
|
|
|
buckets[i] = start
|
|
|
|
|
start *= factor
|
|
|
|
|
}
|
|
|
|
|
return buckets
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HistogramOpts bundles the options for creating a Histogram metric. It is
|
2018-09-17 13:07:31 +03:00
|
|
|
|
// mandatory to set Name to a non-empty string. All other fields are optional
|
|
|
|
|
// and can safely be left at their zero value, although it is strongly
|
|
|
|
|
// encouraged to set a Help string.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
type HistogramOpts struct {
|
|
|
|
|
// Namespace, Subsystem, and Name are components of the fully-qualified
|
|
|
|
|
// name of the Histogram (created by joining these components with
|
|
|
|
|
// "_"). Only Name is mandatory, the others merely help structuring the
|
|
|
|
|
// name. Note that the fully-qualified name of the Histogram must be a
|
|
|
|
|
// valid Prometheus metric name.
|
|
|
|
|
Namespace string
|
|
|
|
|
Subsystem string
|
|
|
|
|
Name string
|
|
|
|
|
|
2018-09-17 13:07:31 +03:00
|
|
|
|
// Help provides information about this Histogram.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
//
|
|
|
|
|
// Metrics with the same fully-qualified name must have the same Help
|
|
|
|
|
// string.
|
|
|
|
|
Help string
|
|
|
|
|
|
2017-08-30 02:05:29 +03:00
|
|
|
|
// ConstLabels are used to attach fixed labels to this metric. Metrics
|
|
|
|
|
// with the same fully-qualified name must have the same label names in
|
|
|
|
|
// their ConstLabels.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
//
|
2017-08-30 02:05:29 +03:00
|
|
|
|
// ConstLabels are only used rarely. In particular, do not use them to
|
|
|
|
|
// attach the same labels to all your metrics. Those use cases are
|
|
|
|
|
// better covered by target labels set by the scraping Prometheus
|
|
|
|
|
// server, or by one specific metric (e.g. a build_info or a
|
|
|
|
|
// machine_role metric). See also
|
|
|
|
|
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
|
2015-02-18 21:23:34 +03:00
|
|
|
|
ConstLabels Labels
|
|
|
|
|
|
|
|
|
|
// Buckets defines the buckets into which observations are counted. 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, it will be added
|
2015-05-30 18:30:45 +03:00
|
|
|
|
// implicitly. The default value is DefBuckets.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
Buckets []float64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewHistogram creates a new Histogram based on the provided HistogramOpts. It
|
|
|
|
|
// panics if the buckets in HistogramOpts are not in strictly increasing order.
|
|
|
|
|
func NewHistogram(opts HistogramOpts) Histogram {
|
|
|
|
|
return newHistogram(
|
|
|
|
|
NewDesc(
|
|
|
|
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
|
|
|
opts.Help,
|
|
|
|
|
nil,
|
|
|
|
|
opts.ConstLabels,
|
|
|
|
|
),
|
|
|
|
|
opts,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
|
|
|
|
|
if len(desc.variableLabels) != len(labelValues) {
|
2018-10-31 20:59:13 +03:00
|
|
|
|
err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, labelValues)
|
|
|
|
|
panic(err)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, n := range desc.variableLabels {
|
2015-08-23 14:51:32 +03:00
|
|
|
|
if n == bucketLabel {
|
2015-02-19 17:31:43 +03:00
|
|
|
|
panic(errBucketLabelNotAllowed)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, lp := range desc.constLabelPairs {
|
2015-08-23 14:51:32 +03:00
|
|
|
|
if lp.GetName() == bucketLabel {
|
2015-02-19 17:31:43 +03:00
|
|
|
|
panic(errBucketLabelNotAllowed)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(opts.Buckets) == 0 {
|
|
|
|
|
opts.Buckets = DefBuckets
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h := &histogram{
|
|
|
|
|
desc: desc,
|
|
|
|
|
upperBounds: opts.Buckets,
|
|
|
|
|
labelPairs: makeLabelPairs(desc, labelValues),
|
2018-09-24 14:28:13 +03:00
|
|
|
|
counts: [2]*histogramCounts{&histogramCounts{}, &histogramCounts{}},
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
for i, upperBound := range h.upperBounds {
|
|
|
|
|
if i < len(h.upperBounds)-1 {
|
|
|
|
|
if upperBound >= h.upperBounds[i+1] {
|
|
|
|
|
panic(fmt.Errorf(
|
|
|
|
|
"histogram buckets must be in increasing order: %f >= %f",
|
|
|
|
|
upperBound, h.upperBounds[i+1],
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if math.IsInf(upperBound, +1) {
|
|
|
|
|
// The +Inf bucket is implicit. Remove it here.
|
|
|
|
|
h.upperBounds = h.upperBounds[:i]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-07 17:20:30 +03:00
|
|
|
|
// Finally we know the final length of h.upperBounds and can make counts
|
|
|
|
|
// for both states:
|
|
|
|
|
h.counts[0].buckets = make([]uint64, len(h.upperBounds))
|
|
|
|
|
h.counts[1].buckets = make([]uint64, len(h.upperBounds))
|
2015-02-18 21:23:34 +03:00
|
|
|
|
|
2016-08-03 02:09:27 +03:00
|
|
|
|
h.init(h) // Init self-collection.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
return h
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-07 17:20:30 +03:00
|
|
|
|
type histogramCounts struct {
|
2015-05-21 13:19:38 +03:00
|
|
|
|
// sumBits contains the bits of the float64 representing the sum of all
|
|
|
|
|
// observations. sumBits and count have to go first in the struct to
|
|
|
|
|
// guarantee alignment for atomic operations.
|
|
|
|
|
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
|
|
|
|
sumBits uint64
|
|
|
|
|
count uint64
|
2018-09-07 17:20:30 +03:00
|
|
|
|
buckets []uint64
|
|
|
|
|
}
|
2015-05-21 13:19:38 +03:00
|
|
|
|
|
2018-09-07 17:20:30 +03:00
|
|
|
|
type histogram struct {
|
2018-09-24 14:28:13 +03:00
|
|
|
|
// countAndHotIdx is a complicated one. For lock-free yet atomic
|
|
|
|
|
// observations, we need to save the total count of observations again,
|
|
|
|
|
// combined with the index of the currently-hot counts struct, so that
|
|
|
|
|
// we can perform the operation on both values atomically. The least
|
|
|
|
|
// significant bit defines the hot counts struct. The remaining 63 bits
|
|
|
|
|
// represent the total count of observations. This happens under the
|
|
|
|
|
// assumption that the 63bit count will never overflow. Rationale: An
|
|
|
|
|
// observations takes about 30ns. Let's assume it could happen in
|
|
|
|
|
// 10ns. Overflowing the counter will then take at least (2^63)*10ns,
|
|
|
|
|
// which is about 3000 years.
|
|
|
|
|
//
|
|
|
|
|
// This has to be first in the struct for 64bit alignment. See
|
|
|
|
|
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
|
|
|
|
countAndHotIdx uint64
|
|
|
|
|
|
2016-08-03 02:09:27 +03:00
|
|
|
|
selfCollector
|
2018-09-07 17:20:30 +03:00
|
|
|
|
desc *Desc
|
|
|
|
|
writeMtx sync.Mutex // Only used in the Write method.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
|
|
|
|
|
upperBounds []float64
|
2018-09-07 17:20:30 +03:00
|
|
|
|
|
|
|
|
|
// Two counts, one is "hot" for lock-free observations, the other is
|
2018-09-24 14:28:13 +03:00
|
|
|
|
// "cold" for writing out a dto.Metric. It has to be an array of
|
|
|
|
|
// pointers to guarantee 64bit alignment of the histogramCounts, see
|
|
|
|
|
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
|
|
|
|
|
counts [2]*histogramCounts
|
2018-09-07 17:20:30 +03:00
|
|
|
|
hotIdx int // Index of currently-hot counts. Only used within Write.
|
|
|
|
|
|
2015-02-18 21:23:34 +03:00
|
|
|
|
labelPairs []*dto.LabelPair
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *histogram) Desc() *Desc {
|
|
|
|
|
return h.desc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *histogram) Observe(v float64) {
|
2015-02-19 21:50:14 +03:00
|
|
|
|
// TODO(beorn7): For small numbers of buckets (<30), a linear search is
|
|
|
|
|
// slightly faster than the binary search. If we really care, we could
|
|
|
|
|
// switch from one search strategy to the other depending on the number
|
|
|
|
|
// of buckets.
|
|
|
|
|
//
|
|
|
|
|
// Microbenchmarks (BenchmarkHistogramNoLabels):
|
|
|
|
|
// 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op
|
|
|
|
|
// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
|
|
|
|
|
// 300 buckets: 154 ns/op linear - binary 61.6 ns/op
|
|
|
|
|
i := sort.SearchFloat64s(h.upperBounds, v)
|
2018-09-07 17:20:30 +03:00
|
|
|
|
|
|
|
|
|
// We increment h.countAndHotIdx by 2 so that the counter in the upper
|
|
|
|
|
// 63 bits gets incremented by 1. At the same time, we get the new value
|
|
|
|
|
// back, which we can use to find the currently-hot counts.
|
|
|
|
|
n := atomic.AddUint64(&h.countAndHotIdx, 2)
|
2018-09-24 14:28:13 +03:00
|
|
|
|
hotCounts := h.counts[n%2]
|
2018-09-07 17:20:30 +03:00
|
|
|
|
|
|
|
|
|
if i < len(h.upperBounds) {
|
|
|
|
|
atomic.AddUint64(&hotCounts.buckets[i], 1)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
for {
|
2018-09-07 17:20:30 +03:00
|
|
|
|
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
|
2018-09-07 17:20:30 +03:00
|
|
|
|
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
|
2015-02-18 21:23:34 +03:00
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-07 17:20:30 +03:00
|
|
|
|
// Increment count last as we take it as a signal that the observation
|
|
|
|
|
// is complete.
|
|
|
|
|
atomic.AddUint64(&hotCounts.count, 1)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *histogram) Write(out *dto.Metric) error {
|
2018-09-07 17:20:30 +03:00
|
|
|
|
var (
|
|
|
|
|
his = &dto.Histogram{}
|
|
|
|
|
buckets = make([]*dto.Bucket, len(h.upperBounds))
|
|
|
|
|
hotCounts, coldCounts *histogramCounts
|
|
|
|
|
count uint64
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// For simplicity, we mutex the rest of this method. It is not in the
|
|
|
|
|
// hot path, i.e. Observe is called much more often than Write. The
|
|
|
|
|
// complication of making Write lock-free isn't worth it.
|
|
|
|
|
h.writeMtx.Lock()
|
|
|
|
|
defer h.writeMtx.Unlock()
|
2015-02-18 21:23:34 +03:00
|
|
|
|
|
2018-09-07 17:20:30 +03:00
|
|
|
|
// This is a bit arcane, which is why the following spells out this if
|
|
|
|
|
// clause in English:
|
|
|
|
|
//
|
|
|
|
|
// If the currently-hot counts struct is #0, we atomically increment
|
|
|
|
|
// h.countAndHotIdx by 1 so that from now on Observe will use the counts
|
|
|
|
|
// struct #1. Furthermore, the atomic increment gives us the new value,
|
|
|
|
|
// which, in its most significant 63 bits, tells us the count of
|
|
|
|
|
// observations done so far up to and including currently ongoing
|
|
|
|
|
// observations still using the counts struct just changed from hot to
|
|
|
|
|
// cold. To have a normal uint64 for the count, we bitshift by 1 and
|
|
|
|
|
// save the result in count. We also set h.hotIdx to 1 for the next
|
|
|
|
|
// Write call, and we will refer to counts #1 as hotCounts and to counts
|
|
|
|
|
// #0 as coldCounts.
|
|
|
|
|
//
|
|
|
|
|
// If the currently-hot counts struct is #1, we do the corresponding
|
|
|
|
|
// things the other way round. We have to _decrement_ h.countAndHotIdx
|
|
|
|
|
// (which is a bit arcane in itself, as we have to express -1 with an
|
|
|
|
|
// unsigned int...).
|
|
|
|
|
if h.hotIdx == 0 {
|
|
|
|
|
count = atomic.AddUint64(&h.countAndHotIdx, 1) >> 1
|
|
|
|
|
h.hotIdx = 1
|
2018-09-24 14:28:13 +03:00
|
|
|
|
hotCounts = h.counts[1]
|
|
|
|
|
coldCounts = h.counts[0]
|
2018-09-07 17:20:30 +03:00
|
|
|
|
} else {
|
|
|
|
|
count = atomic.AddUint64(&h.countAndHotIdx, ^uint64(0)) >> 1 // Decrement.
|
|
|
|
|
h.hotIdx = 0
|
2018-09-24 14:28:13 +03:00
|
|
|
|
hotCounts = h.counts[0]
|
|
|
|
|
coldCounts = h.counts[1]
|
2018-09-07 17:20:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now we have to wait for the now-declared-cold counts to actually cool
|
|
|
|
|
// down, i.e. wait for all observations still using it to finish. That's
|
|
|
|
|
// the case once the count in the cold counts struct is the same as the
|
|
|
|
|
// one atomically retrieved from the upper 63bits of h.countAndHotIdx.
|
|
|
|
|
for {
|
|
|
|
|
if count == atomic.LoadUint64(&coldCounts.count) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
runtime.Gosched() // Let observations get work done.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
his.SampleCount = proto.Uint64(count)
|
|
|
|
|
his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits)))
|
|
|
|
|
var cumCount uint64
|
2015-02-18 21:23:34 +03:00
|
|
|
|
for i, upperBound := range h.upperBounds {
|
2018-09-07 17:20:30 +03:00
|
|
|
|
cumCount += atomic.LoadUint64(&coldCounts.buckets[i])
|
2015-02-18 21:23:34 +03:00
|
|
|
|
buckets[i] = &dto.Bucket{
|
2018-09-07 17:20:30 +03:00
|
|
|
|
CumulativeCount: proto.Uint64(cumCount),
|
2015-02-18 21:23:34 +03:00
|
|
|
|
UpperBound: proto.Float64(upperBound),
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-07 17:20:30 +03:00
|
|
|
|
|
2015-02-18 21:23:34 +03:00
|
|
|
|
his.Bucket = buckets
|
|
|
|
|
out.Histogram = his
|
|
|
|
|
out.Label = h.labelPairs
|
2018-09-07 17:20:30 +03:00
|
|
|
|
|
|
|
|
|
// Finally add all the cold counts to the new hot counts and reset the cold counts.
|
|
|
|
|
atomic.AddUint64(&hotCounts.count, count)
|
|
|
|
|
atomic.StoreUint64(&coldCounts.count, 0)
|
|
|
|
|
for {
|
|
|
|
|
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
|
|
|
|
|
newBits := math.Float64bits(math.Float64frombits(oldBits) + his.GetSampleSum())
|
|
|
|
|
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
|
|
|
|
|
atomic.StoreUint64(&coldCounts.sumBits, 0)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for i := range h.upperBounds {
|
|
|
|
|
atomic.AddUint64(&hotCounts.buckets[i], atomic.LoadUint64(&coldCounts.buckets[i]))
|
|
|
|
|
atomic.StoreUint64(&coldCounts.buckets[i], 0)
|
|
|
|
|
}
|
2015-02-18 21:23:34 +03:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HistogramVec is a Collector that bundles a set of Histograms that all share the
|
|
|
|
|
// same Desc, but have different values for their variable labels. This is used
|
|
|
|
|
// if you want to count the same thing partitioned by various dimensions
|
2015-02-19 17:34:04 +03:00
|
|
|
|
// (e.g. HTTP request latencies, partitioned by status code and method). Create
|
2015-02-18 21:23:34 +03:00
|
|
|
|
// instances with NewHistogramVec.
|
|
|
|
|
type HistogramVec struct {
|
2017-06-28 18:55:59 +03:00
|
|
|
|
*metricVec
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
|
2017-06-28 18:55:59 +03:00
|
|
|
|
// partitioned by the given label names.
|
2015-02-18 21:23:34 +03:00
|
|
|
|
func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
|
|
|
|
|
desc := NewDesc(
|
|
|
|
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
|
|
|
opts.Help,
|
|
|
|
|
labelNames,
|
|
|
|
|
opts.ConstLabels,
|
|
|
|
|
)
|
|
|
|
|
return &HistogramVec{
|
2017-06-28 18:55:59 +03:00
|
|
|
|
metricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
2016-08-11 06:03:15 +03:00
|
|
|
|
return newHistogram(desc, opts, lvs...)
|
|
|
|
|
}),
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-28 18:55:59 +03:00
|
|
|
|
// GetMetricWithLabelValues returns the Histogram for the given slice of label
|
|
|
|
|
// values (same order as the VariableLabels in Desc). If that combination of
|
|
|
|
|
// label values is accessed for the first time, a new Histogram is created.
|
|
|
|
|
//
|
|
|
|
|
// It is possible to call this method without using the returned Histogram to only
|
|
|
|
|
// create the new Histogram but leave it at its starting value, a Histogram without
|
|
|
|
|
// any observations.
|
|
|
|
|
//
|
|
|
|
|
// Keeping the Histogram for later use is possible (and should be considered if
|
|
|
|
|
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
|
|
|
|
|
// Delete can be used to delete the Histogram from the HistogramVec. In that case, the
|
|
|
|
|
// Histogram will still exist, but it will not be exported anymore, even if a
|
|
|
|
|
// Histogram with the same label values is created later. See also the CounterVec
|
|
|
|
|
// example.
|
|
|
|
|
//
|
|
|
|
|
// An error is returned if the number of label values is not the same as the
|
2017-08-30 02:05:29 +03:00
|
|
|
|
// number of VariableLabels in Desc (minus any curried labels).
|
2017-06-28 18:55:59 +03:00
|
|
|
|
//
|
|
|
|
|
// Note that for more than one label value, this method is prone to mistakes
|
|
|
|
|
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
|
|
|
|
|
// an alternative to avoid that type of mistake. For higher label numbers, the
|
|
|
|
|
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
|
|
|
|
// with a performance overhead (for creating and processing the Labels map).
|
|
|
|
|
// See also the GaugeVec example.
|
2017-08-29 15:51:49 +03:00
|
|
|
|
func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
|
|
|
|
|
metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
if metric != nil {
|
2017-04-24 22:13:19 +03:00
|
|
|
|
return metric.(Observer), err
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-28 18:55:59 +03:00
|
|
|
|
// GetMetricWith returns the Histogram for the given Labels map (the label names
|
|
|
|
|
// must match those of the VariableLabels in Desc). If that label map is
|
|
|
|
|
// accessed for the first time, a new Histogram is created. Implications of
|
|
|
|
|
// creating a Histogram without using it and keeping the Histogram for later use
|
|
|
|
|
// are the same as for GetMetricWithLabelValues.
|
|
|
|
|
//
|
|
|
|
|
// An error is returned if the number and names of the Labels are inconsistent
|
2017-08-30 02:05:29 +03:00
|
|
|
|
// with those of the VariableLabels in Desc (minus any curried labels).
|
2017-06-28 18:55:59 +03:00
|
|
|
|
//
|
|
|
|
|
// This method is used for the same purpose as
|
|
|
|
|
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
|
|
|
|
|
// methods.
|
2017-08-29 15:51:49 +03:00
|
|
|
|
func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
|
|
|
|
|
metric, err := v.metricVec.getMetricWith(labels)
|
2015-02-18 21:23:34 +03:00
|
|
|
|
if metric != nil {
|
2017-04-24 22:13:19 +03:00
|
|
|
|
return metric.(Observer), err
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
2017-08-30 02:05:29 +03:00
|
|
|
|
// GetMetricWithLabelValues would have returned an error. Not returning an
|
|
|
|
|
// error allows shortcuts like
|
2015-02-18 21:23:34 +03:00
|
|
|
|
// myVec.WithLabelValues("404", "GET").Observe(42.21)
|
2017-08-29 15:51:49 +03:00
|
|
|
|
func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
|
|
|
|
|
h, err := v.GetMetricWithLabelValues(lvs...)
|
2017-08-29 15:43:37 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
return h
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-30 02:05:29 +03:00
|
|
|
|
// With works as GetMetricWith but panics where GetMetricWithLabels would have
|
|
|
|
|
// returned an error. Not returning an error allows shortcuts like
|
|
|
|
|
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
|
2017-08-29 15:51:49 +03:00
|
|
|
|
func (v *HistogramVec) With(labels Labels) Observer {
|
|
|
|
|
h, err := v.GetMetricWith(labels)
|
2017-08-29 15:43:37 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
return h
|
2015-02-18 21:23:34 +03:00
|
|
|
|
}
|
2015-05-04 01:32:15 +03:00
|
|
|
|
|
2017-08-30 02:05:29 +03:00
|
|
|
|
// CurryWith returns a vector curried with the provided labels, i.e. the
|
|
|
|
|
// returned vector has those labels pre-set for all labeled operations performed
|
|
|
|
|
// on it. The cardinality of the curried vector is reduced accordingly. The
|
|
|
|
|
// order of the remaining labels stays the same (just with the curried labels
|
|
|
|
|
// taken out of the sequence – which is relevant for the
|
|
|
|
|
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
|
|
|
|
|
// vector, but only with labels not yet used for currying before.
|
|
|
|
|
//
|
|
|
|
|
// The metrics contained in the HistogramVec are shared between the curried and
|
|
|
|
|
// uncurried vectors. They are just accessed differently. Curried and uncurried
|
|
|
|
|
// vectors behave identically in terms of collection. Only one must be
|
|
|
|
|
// registered with a given registry (usually the uncurried version). The Reset
|
|
|
|
|
// method deletes all metrics, even if called on a curried vector.
|
2017-12-22 18:11:58 +03:00
|
|
|
|
func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
|
2017-08-30 02:05:29 +03:00
|
|
|
|
vec, err := v.curryWith(labels)
|
|
|
|
|
if vec != nil {
|
|
|
|
|
return &HistogramVec{vec}, err
|
|
|
|
|
}
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MustCurryWith works as CurryWith but panics where CurryWith would have
|
|
|
|
|
// returned an error.
|
2017-12-22 18:11:58 +03:00
|
|
|
|
func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec {
|
2017-08-30 02:05:29 +03:00
|
|
|
|
vec, err := v.CurryWith(labels)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
return vec
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-04 01:32:15 +03:00
|
|
|
|
type constHistogram struct {
|
|
|
|
|
desc *Desc
|
|
|
|
|
count uint64
|
|
|
|
|
sum float64
|
|
|
|
|
buckets map[float64]uint64
|
|
|
|
|
labelPairs []*dto.LabelPair
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *constHistogram) Desc() *Desc {
|
|
|
|
|
return h.desc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *constHistogram) Write(out *dto.Metric) error {
|
|
|
|
|
his := &dto.Histogram{}
|
|
|
|
|
buckets := make([]*dto.Bucket, 0, len(h.buckets))
|
|
|
|
|
|
|
|
|
|
his.SampleCount = proto.Uint64(h.count)
|
|
|
|
|
his.SampleSum = proto.Float64(h.sum)
|
|
|
|
|
|
|
|
|
|
for upperBound, count := range h.buckets {
|
|
|
|
|
buckets = append(buckets, &dto.Bucket{
|
|
|
|
|
CumulativeCount: proto.Uint64(count),
|
|
|
|
|
UpperBound: proto.Float64(upperBound),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(buckets) > 0 {
|
|
|
|
|
sort.Sort(buckSort(buckets))
|
|
|
|
|
}
|
|
|
|
|
his.Bucket = buckets
|
|
|
|
|
|
|
|
|
|
out.Histogram = his
|
|
|
|
|
out.Label = h.labelPairs
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-04 14:13:06 +03:00
|
|
|
|
// NewConstHistogram returns a metric representing a Prometheus histogram with
|
|
|
|
|
// fixed values for the count, sum, and bucket counts. As those parameters
|
|
|
|
|
// cannot be changed, the returned value does not implement the Histogram
|
|
|
|
|
// interface (but only the Metric interface). Users of this package will not
|
|
|
|
|
// have much use for it in regular operations. However, when implementing custom
|
|
|
|
|
// Collectors, it is useful as a throw-away metric that is generated on the fly
|
|
|
|
|
// to send it to Prometheus in the Collect method.
|
2015-05-04 01:32:15 +03:00
|
|
|
|
//
|
|
|
|
|
// buckets is a map of upper bounds to cumulative counts, excluding the +Inf
|
|
|
|
|
// bucket.
|
|
|
|
|
//
|
|
|
|
|
// NewConstHistogram returns an error if the length of labelValues is not
|
2018-09-17 12:50:42 +03:00
|
|
|
|
// consistent with the variable labels in Desc or if Desc is invalid.
|
2015-05-04 01:32:15 +03:00
|
|
|
|
func NewConstHistogram(
|
|
|
|
|
desc *Desc,
|
|
|
|
|
count uint64,
|
|
|
|
|
sum float64,
|
|
|
|
|
buckets map[float64]uint64,
|
|
|
|
|
labelValues ...string,
|
|
|
|
|
) (Metric, error) {
|
2018-09-17 12:50:42 +03:00
|
|
|
|
if desc.err != nil {
|
|
|
|
|
return nil, desc.err
|
|
|
|
|
}
|
2017-08-25 18:58:59 +03:00
|
|
|
|
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
|
2017-08-19 23:57:48 +03:00
|
|
|
|
return nil, err
|
2015-05-04 01:32:15 +03:00
|
|
|
|
}
|
|
|
|
|
return &constHistogram{
|
|
|
|
|
desc: desc,
|
|
|
|
|
count: count,
|
|
|
|
|
sum: sum,
|
|
|
|
|
buckets: buckets,
|
|
|
|
|
labelPairs: makeLabelPairs(desc, labelValues),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MustNewConstHistogram is a version of NewConstHistogram that panics where
|
|
|
|
|
// NewConstMetric would have returned an error.
|
|
|
|
|
func MustNewConstHistogram(
|
|
|
|
|
desc *Desc,
|
|
|
|
|
count uint64,
|
|
|
|
|
sum float64,
|
|
|
|
|
buckets map[float64]uint64,
|
|
|
|
|
labelValues ...string,
|
|
|
|
|
) Metric {
|
|
|
|
|
m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
return m
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type buckSort []*dto.Bucket
|
|
|
|
|
|
|
|
|
|
func (s buckSort) Len() int {
|
|
|
|
|
return len(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s buckSort) Swap(i, j int) {
|
|
|
|
|
s[i], s[j] = s[j], s[i]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s buckSort) Less(i, j int) bool {
|
|
|
|
|
return s[i].GetUpperBound() < s[j].GetUpperBound()
|
|
|
|
|
}
|