123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637 |
- package prometheus
- import (
- "fmt"
- "math"
- "runtime"
- "sort"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/golang/protobuf/proto"
- dto "github.com/prometheus/client_model/go"
- )
- type Histogram interface {
- Metric
- Collector
-
- Observe(float64)
- }
- const bucketLabel = "le"
- var (
- DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
- errBucketLabelNotAllowed = fmt.Errorf(
- "%q is not allowed as label name in histograms", bucketLabel,
- )
- )
- func LinearBuckets(start, width float64, count int) []float64 {
- if count < 1 {
- panic("LinearBuckets needs a positive count")
- }
- buckets := make([]float64, count)
- for i := range buckets {
- buckets[i] = start
- start += width
- }
- return buckets
- }
- func ExponentialBuckets(start, factor float64, count int) []float64 {
- if count < 1 {
- panic("ExponentialBuckets needs a positive count")
- }
- if start <= 0 {
- panic("ExponentialBuckets needs a positive start value")
- }
- if factor <= 1 {
- panic("ExponentialBuckets needs a factor greater than 1")
- }
- buckets := make([]float64, count)
- for i := range buckets {
- buckets[i] = start
- start *= factor
- }
- return buckets
- }
- type HistogramOpts struct {
-
-
-
-
-
- Namespace string
- Subsystem string
- Name string
-
-
-
-
- Help string
-
-
-
-
-
-
-
-
-
-
- ConstLabels Labels
-
-
-
-
-
- Buckets []float64
- }
- 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) {
- panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
- }
- for _, n := range desc.variableLabels {
- if n == bucketLabel {
- panic(errBucketLabelNotAllowed)
- }
- }
- for _, lp := range desc.constLabelPairs {
- if lp.GetName() == bucketLabel {
- panic(errBucketLabelNotAllowed)
- }
- }
- if len(opts.Buckets) == 0 {
- opts.Buckets = DefBuckets
- }
- h := &histogram{
- desc: desc,
- upperBounds: opts.Buckets,
- labelPairs: makeLabelPairs(desc, labelValues),
- counts: [2]*histogramCounts{{}, {}},
- now: time.Now,
- }
- 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) {
-
- h.upperBounds = h.upperBounds[:i]
- }
- }
- }
-
-
- h.counts[0].buckets = make([]uint64, len(h.upperBounds))
- h.counts[1].buckets = make([]uint64, len(h.upperBounds))
- h.exemplars = make([]atomic.Value, len(h.upperBounds)+1)
- h.init(h)
- return h
- }
- type histogramCounts struct {
-
-
-
-
- sumBits uint64
- count uint64
- buckets []uint64
- }
- type histogram struct {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- countAndHotIdx uint64
- selfCollector
- desc *Desc
- writeMtx sync.Mutex
-
-
-
-
- counts [2]*histogramCounts
- upperBounds []float64
- labelPairs []*dto.LabelPair
- exemplars []atomic.Value
- now func() time.Time // To mock out time.Now() for testing.
- }
- func (h *histogram) Desc() *Desc {
- return h.desc
- }
- func (h *histogram) Observe(v float64) {
- h.observe(v, h.findBucket(v))
- }
- func (h *histogram) ObserveWithExemplar(v float64, e Labels) {
- i := h.findBucket(v)
- h.observe(v, i)
- h.updateExemplar(v, i, e)
- }
- func (h *histogram) Write(out *dto.Metric) error {
-
-
-
-
- h.writeMtx.Lock()
- defer h.writeMtx.Unlock()
-
-
-
- n := atomic.AddUint64(&h.countAndHotIdx, 1<<63)
-
- count := n & ((1 << 63) - 1)
-
-
- hotCounts := h.counts[n>>63]
- coldCounts := h.counts[(^n)>>63]
-
- for count != atomic.LoadUint64(&coldCounts.count) {
- runtime.Gosched()
- }
- his := &dto.Histogram{
- Bucket: make([]*dto.Bucket, len(h.upperBounds)),
- SampleCount: proto.Uint64(count),
- SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
- }
- var cumCount uint64
- for i, upperBound := range h.upperBounds {
- cumCount += atomic.LoadUint64(&coldCounts.buckets[i])
- his.Bucket[i] = &dto.Bucket{
- CumulativeCount: proto.Uint64(cumCount),
- UpperBound: proto.Float64(upperBound),
- }
- if e := h.exemplars[i].Load(); e != nil {
- his.Bucket[i].Exemplar = e.(*dto.Exemplar)
- }
- }
-
- if e := h.exemplars[len(h.upperBounds)].Load(); e != nil {
- b := &dto.Bucket{
- CumulativeCount: proto.Uint64(count),
- UpperBound: proto.Float64(math.Inf(1)),
- Exemplar: e.(*dto.Exemplar),
- }
- his.Bucket = append(his.Bucket, b)
- }
- out.Histogram = his
- out.Label = h.labelPairs
-
- 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)
- }
- return nil
- }
- func (h *histogram) findBucket(v float64) int {
-
-
-
-
-
-
-
-
-
- return sort.SearchFloat64s(h.upperBounds, v)
- }
- func (h *histogram) observe(v float64, bucket int) {
-
-
-
- n := atomic.AddUint64(&h.countAndHotIdx, 1)
- hotCounts := h.counts[n>>63]
- if bucket < len(h.upperBounds) {
- atomic.AddUint64(&hotCounts.buckets[bucket], 1)
- }
- for {
- oldBits := atomic.LoadUint64(&hotCounts.sumBits)
- newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
- if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
- break
- }
- }
-
-
- atomic.AddUint64(&hotCounts.count, 1)
- }
- func (h *histogram) updateExemplar(v float64, bucket int, l Labels) {
- if l == nil {
- return
- }
- e, err := newExemplar(v, h.now(), l)
- if err != nil {
- panic(err)
- }
- h.exemplars[bucket].Store(e)
- }
- type HistogramVec struct {
- *metricVec
- }
- func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
- desc := NewDesc(
- BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
- opts.Help,
- labelNames,
- opts.ConstLabels,
- )
- return &HistogramVec{
- metricVec: newMetricVec(desc, func(lvs ...string) Metric {
- return newHistogram(desc, opts, lvs...)
- }),
- }
- }
- func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
- metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
- if metric != nil {
- return metric.(Observer), err
- }
- return nil, err
- }
- func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
- metric, err := v.metricVec.getMetricWith(labels)
- if metric != nil {
- return metric.(Observer), err
- }
- return nil, err
- }
- func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
- h, err := v.GetMetricWithLabelValues(lvs...)
- if err != nil {
- panic(err)
- }
- return h
- }
- func (v *HistogramVec) With(labels Labels) Observer {
- h, err := v.GetMetricWith(labels)
- if err != nil {
- panic(err)
- }
- return h
- }
- func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
- vec, err := v.curryWith(labels)
- if vec != nil {
- return &HistogramVec{vec}, err
- }
- return nil, err
- }
- func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec {
- vec, err := v.CurryWith(labels)
- if err != nil {
- panic(err)
- }
- return vec
- }
- 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
- }
- func NewConstHistogram(
- desc *Desc,
- count uint64,
- sum float64,
- buckets map[float64]uint64,
- labelValues ...string,
- ) (Metric, error) {
- if desc.err != nil {
- return nil, desc.err
- }
- if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
- return nil, err
- }
- return &constHistogram{
- desc: desc,
- count: count,
- sum: sum,
- buckets: buckets,
- labelPairs: makeLabelPairs(desc, labelValues),
- }, nil
- }
- 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()
- }
|