|
@@ -22,10 +22,10 @@ import (
|
|
"sync/atomic"
|
|
"sync/atomic"
|
|
"time"
|
|
"time"
|
|
|
|
|
|
- //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
|
|
|
- "github.com/golang/protobuf/proto"
|
|
|
|
-
|
|
|
|
dto "github.com/prometheus/client_model/go"
|
|
dto "github.com/prometheus/client_model/go"
|
|
|
|
+
|
|
|
|
+ "google.golang.org/protobuf/proto"
|
|
|
|
+ "google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
)
|
|
|
|
|
|
// nativeHistogramBounds for the frac of observed values. Only relevant for
|
|
// nativeHistogramBounds for the frac of observed values. Only relevant for
|
|
@@ -392,7 +392,7 @@ type HistogramOpts struct {
|
|
// zero, it is replaced by default buckets. The default buckets are
|
|
// zero, it is replaced by default buckets. The default buckets are
|
|
// DefBuckets if no buckets for a native histogram (see below) are used,
|
|
// DefBuckets if no buckets for a native histogram (see below) are used,
|
|
// otherwise the default is no buckets. (In other words, if you want to
|
|
// otherwise the default is no buckets. (In other words, if you want to
|
|
- // use both reguler buckets and buckets for a native histogram, you have
|
|
|
|
|
|
+ // use both regular buckets and buckets for a native histogram, you have
|
|
// to define the regular buckets here explicitly.)
|
|
// to define the regular buckets here explicitly.)
|
|
Buckets []float64
|
|
Buckets []float64
|
|
|
|
|
|
@@ -402,7 +402,7 @@ type HistogramOpts struct {
|
|
// Histogram by a Prometheus server with that feature enabled (requires
|
|
// Histogram by a Prometheus server with that feature enabled (requires
|
|
// Prometheus v2.40+). Sparse buckets are exponential buckets covering
|
|
// Prometheus v2.40+). Sparse buckets are exponential buckets covering
|
|
// the whole float64 range (with the exception of the “zero” bucket, see
|
|
// the whole float64 range (with the exception of the “zero” bucket, see
|
|
- // SparseBucketsZeroThreshold below). From any one bucket to the next,
|
|
|
|
|
|
+ // NativeHistogramZeroThreshold below). From any one bucket to the next,
|
|
// the width of the bucket grows by a constant
|
|
// the width of the bucket grows by a constant
|
|
// factor. NativeHistogramBucketFactor provides an upper bound for this
|
|
// factor. NativeHistogramBucketFactor provides an upper bound for this
|
|
// factor (exception see below). The smaller
|
|
// factor (exception see below). The smaller
|
|
@@ -414,8 +414,8 @@ type HistogramOpts struct {
|
|
// and 2, same as between 2 and 4, and 4 and 8, etc.).
|
|
// and 2, same as between 2 and 4, and 4 and 8, etc.).
|
|
//
|
|
//
|
|
// Details about the actually used factor: The factor is calculated as
|
|
// Details about the actually used factor: The factor is calculated as
|
|
- // 2^(2^n), where n is an integer number between (and including) -8 and
|
|
|
|
- // 4. n is chosen so that the resulting factor is the largest that is
|
|
|
|
|
|
+ // 2^(2^-n), where n is an integer number between (and including) -4 and
|
|
|
|
+ // 8. n is chosen so that the resulting factor is the largest that is
|
|
// still smaller or equal to NativeHistogramBucketFactor. Note that the
|
|
// still smaller or equal to NativeHistogramBucketFactor. Note that the
|
|
// smallest possible factor is therefore approx. 1.00271 (i.e. 2^(2^-8)
|
|
// smallest possible factor is therefore approx. 1.00271 (i.e. 2^(2^-8)
|
|
// ). If NativeHistogramBucketFactor is greater than 1 but smaller than
|
|
// ). If NativeHistogramBucketFactor is greater than 1 but smaller than
|
|
@@ -429,12 +429,12 @@ type HistogramOpts struct {
|
|
// a major version bump.
|
|
// a major version bump.
|
|
NativeHistogramBucketFactor float64
|
|
NativeHistogramBucketFactor float64
|
|
// All observations with an absolute value of less or equal
|
|
// All observations with an absolute value of less or equal
|
|
- // NativeHistogramZeroThreshold are accumulated into a “zero”
|
|
|
|
- // bucket. For best results, this should be close to a bucket
|
|
|
|
- // boundary. This is usually the case if picking a power of two. If
|
|
|
|
|
|
+ // NativeHistogramZeroThreshold are accumulated into a “zero” bucket.
|
|
|
|
+ // For best results, this should be close to a bucket boundary. This is
|
|
|
|
+ // usually the case if picking a power of two. If
|
|
// NativeHistogramZeroThreshold is left at zero,
|
|
// NativeHistogramZeroThreshold is left at zero,
|
|
- // DefSparseBucketsZeroThreshold is used as the threshold. To configure
|
|
|
|
- // a zero bucket with an actual threshold of zero (i.e. only
|
|
|
|
|
|
+ // DefNativeHistogramZeroThreshold is used as the threshold. To
|
|
|
|
+ // configure a zero bucket with an actual threshold of zero (i.e. only
|
|
// observations of precisely zero will go into the zero bucket), set
|
|
// observations of precisely zero will go into the zero bucket), set
|
|
// NativeHistogramZeroThreshold to the NativeHistogramZeroThresholdZero
|
|
// NativeHistogramZeroThreshold to the NativeHistogramZeroThresholdZero
|
|
// constant (or any negative float value).
|
|
// constant (or any negative float value).
|
|
@@ -447,26 +447,46 @@ type HistogramOpts struct {
|
|
// Histogram are sufficiently wide-spread. In particular, this could be
|
|
// Histogram are sufficiently wide-spread. In particular, this could be
|
|
// used as a DoS attack vector. Where the observed values depend on
|
|
// used as a DoS attack vector. Where the observed values depend on
|
|
// external inputs, it is highly recommended to set a
|
|
// external inputs, it is highly recommended to set a
|
|
- // NativeHistogramMaxBucketNumber.) Once the set
|
|
|
|
|
|
+ // NativeHistogramMaxBucketNumber.) Once the set
|
|
// NativeHistogramMaxBucketNumber is exceeded, the following strategy is
|
|
// NativeHistogramMaxBucketNumber is exceeded, the following strategy is
|
|
- // enacted: First, if the last reset (or the creation) of the histogram
|
|
|
|
- // is at least NativeHistogramMinResetDuration ago, then the whole
|
|
|
|
- // histogram is reset to its initial state (including regular
|
|
|
|
- // buckets). If less time has passed, or if
|
|
|
|
- // NativeHistogramMinResetDuration is zero, no reset is
|
|
|
|
- // performed. Instead, the zero threshold is increased sufficiently to
|
|
|
|
- // reduce the number of buckets to or below
|
|
|
|
- // NativeHistogramMaxBucketNumber, but not to more than
|
|
|
|
- // NativeHistogramMaxZeroThreshold. Thus, if
|
|
|
|
- // NativeHistogramMaxZeroThreshold is already at or below the current
|
|
|
|
- // zero threshold, nothing happens at this step. After that, if the
|
|
|
|
- // number of buckets still exceeds NativeHistogramMaxBucketNumber, the
|
|
|
|
- // resolution of the histogram is reduced by doubling the width of the
|
|
|
|
- // sparse buckets (up to a growth factor between one bucket to the next
|
|
|
|
- // of 2^(2^4) = 65536, see above).
|
|
|
|
|
|
+ // enacted:
|
|
|
|
+ // - First, if the last reset (or the creation) of the histogram is at
|
|
|
|
+ // least NativeHistogramMinResetDuration ago, then the whole
|
|
|
|
+ // histogram is reset to its initial state (including regular
|
|
|
|
+ // buckets).
|
|
|
|
+ // - If less time has passed, or if NativeHistogramMinResetDuration is
|
|
|
|
+ // zero, no reset is performed. Instead, the zero threshold is
|
|
|
|
+ // increased sufficiently to reduce the number of buckets to or below
|
|
|
|
+ // NativeHistogramMaxBucketNumber, but not to more than
|
|
|
|
+ // NativeHistogramMaxZeroThreshold. Thus, if
|
|
|
|
+ // NativeHistogramMaxZeroThreshold is already at or below the current
|
|
|
|
+ // zero threshold, nothing happens at this step.
|
|
|
|
+ // - After that, if the number of buckets still exceeds
|
|
|
|
+ // NativeHistogramMaxBucketNumber, the resolution of the histogram is
|
|
|
|
+ // reduced by doubling the width of the sparse buckets (up to a
|
|
|
|
+ // growth factor between one bucket to the next of 2^(2^4) = 65536,
|
|
|
|
+ // see above).
|
|
|
|
+ // - Any increased zero threshold or reduced resolution is reset back
|
|
|
|
+ // to their original values once NativeHistogramMinResetDuration has
|
|
|
|
+ // passed (since the last reset or the creation of the histogram).
|
|
NativeHistogramMaxBucketNumber uint32
|
|
NativeHistogramMaxBucketNumber uint32
|
|
NativeHistogramMinResetDuration time.Duration
|
|
NativeHistogramMinResetDuration time.Duration
|
|
NativeHistogramMaxZeroThreshold float64
|
|
NativeHistogramMaxZeroThreshold float64
|
|
|
|
+
|
|
|
|
+ // now is for testing purposes, by default it's time.Now.
|
|
|
|
+ now func() time.Time
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// HistogramVecOpts bundles the options to create a HistogramVec metric.
|
|
|
|
+// It is mandatory to set HistogramOpts, see there for mandatory fields. VariableLabels
|
|
|
|
+// is optional and can safely be left to its default value.
|
|
|
|
+type HistogramVecOpts struct {
|
|
|
|
+ HistogramOpts
|
|
|
|
+
|
|
|
|
+ // VariableLabels are used to partition the metric vector by the given set
|
|
|
|
+ // of labels. Each label value will be constrained with the optional Constraint
|
|
|
|
+ // function, if provided.
|
|
|
|
+ VariableLabels ConstrainableLabels
|
|
}
|
|
}
|
|
|
|
|
|
// NewHistogram creates a new Histogram based on the provided HistogramOpts. It
|
|
// NewHistogram creates a new Histogram based on the provided HistogramOpts. It
|
|
@@ -488,11 +508,11 @@ func NewHistogram(opts HistogramOpts) Histogram {
|
|
}
|
|
}
|
|
|
|
|
|
func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
|
|
func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
|
|
- if len(desc.variableLabels) != len(labelValues) {
|
|
|
|
- panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
|
|
|
|
|
|
+ if len(desc.variableLabels.names) != len(labelValues) {
|
|
|
|
+ panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, labelValues))
|
|
}
|
|
}
|
|
|
|
|
|
- for _, n := range desc.variableLabels {
|
|
|
|
|
|
+ for _, n := range desc.variableLabels.names {
|
|
if n == bucketLabel {
|
|
if n == bucketLabel {
|
|
panic(errBucketLabelNotAllowed)
|
|
panic(errBucketLabelNotAllowed)
|
|
}
|
|
}
|
|
@@ -503,6 +523,10 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if opts.now == nil {
|
|
|
|
+ opts.now = time.Now
|
|
|
|
+ }
|
|
|
|
+
|
|
h := &histogram{
|
|
h := &histogram{
|
|
desc: desc,
|
|
desc: desc,
|
|
upperBounds: opts.Buckets,
|
|
upperBounds: opts.Buckets,
|
|
@@ -510,8 +534,8 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
|
|
nativeHistogramMaxBuckets: opts.NativeHistogramMaxBucketNumber,
|
|
nativeHistogramMaxBuckets: opts.NativeHistogramMaxBucketNumber,
|
|
nativeHistogramMaxZeroThreshold: opts.NativeHistogramMaxZeroThreshold,
|
|
nativeHistogramMaxZeroThreshold: opts.NativeHistogramMaxZeroThreshold,
|
|
nativeHistogramMinResetDuration: opts.NativeHistogramMinResetDuration,
|
|
nativeHistogramMinResetDuration: opts.NativeHistogramMinResetDuration,
|
|
- lastResetTime: time.Now(),
|
|
|
|
- now: time.Now,
|
|
|
|
|
|
+ lastResetTime: opts.now(),
|
|
|
|
+ now: opts.now,
|
|
}
|
|
}
|
|
if len(h.upperBounds) == 0 && opts.NativeHistogramBucketFactor <= 1 {
|
|
if len(h.upperBounds) == 0 && opts.NativeHistogramBucketFactor <= 1 {
|
|
h.upperBounds = DefBuckets
|
|
h.upperBounds = DefBuckets
|
|
@@ -544,16 +568,12 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
|
|
}
|
|
}
|
|
// Finally we know the final length of h.upperBounds and can make buckets
|
|
// Finally we know the final length of h.upperBounds and can make buckets
|
|
// for both counts as well as exemplars:
|
|
// for both counts as well as exemplars:
|
|
- h.counts[0] = &histogramCounts{
|
|
|
|
- buckets: make([]uint64, len(h.upperBounds)),
|
|
|
|
- nativeHistogramZeroThresholdBits: math.Float64bits(h.nativeHistogramZeroThreshold),
|
|
|
|
- nativeHistogramSchema: h.nativeHistogramSchema,
|
|
|
|
- }
|
|
|
|
- h.counts[1] = &histogramCounts{
|
|
|
|
- buckets: make([]uint64, len(h.upperBounds)),
|
|
|
|
- nativeHistogramZeroThresholdBits: math.Float64bits(h.nativeHistogramZeroThreshold),
|
|
|
|
- nativeHistogramSchema: h.nativeHistogramSchema,
|
|
|
|
- }
|
|
|
|
|
|
+ h.counts[0] = &histogramCounts{buckets: make([]uint64, len(h.upperBounds))}
|
|
|
|
+ atomic.StoreUint64(&h.counts[0].nativeHistogramZeroThresholdBits, math.Float64bits(h.nativeHistogramZeroThreshold))
|
|
|
|
+ atomic.StoreInt32(&h.counts[0].nativeHistogramSchema, h.nativeHistogramSchema)
|
|
|
|
+ h.counts[1] = &histogramCounts{buckets: make([]uint64, len(h.upperBounds))}
|
|
|
|
+ atomic.StoreUint64(&h.counts[1].nativeHistogramZeroThresholdBits, math.Float64bits(h.nativeHistogramZeroThreshold))
|
|
|
|
+ atomic.StoreInt32(&h.counts[1].nativeHistogramSchema, h.nativeHistogramSchema)
|
|
h.exemplars = make([]atomic.Value, len(h.upperBounds)+1)
|
|
h.exemplars = make([]atomic.Value, len(h.upperBounds)+1)
|
|
|
|
|
|
h.init(h) // Init self-collection.
|
|
h.init(h) // Init self-collection.
|
|
@@ -632,8 +652,8 @@ func (hc *histogramCounts) observe(v float64, bucket int, doSparse bool) {
|
|
if frac == 0.5 {
|
|
if frac == 0.5 {
|
|
key--
|
|
key--
|
|
}
|
|
}
|
|
- div := 1 << -schema
|
|
|
|
- key = (key + div - 1) / div
|
|
|
|
|
|
+ offset := (1 << -schema) - 1
|
|
|
|
+ key = (key + offset) >> -schema
|
|
}
|
|
}
|
|
if isInf {
|
|
if isInf {
|
|
key++
|
|
key++
|
|
@@ -694,9 +714,11 @@ type histogram struct {
|
|
nativeHistogramMaxZeroThreshold float64
|
|
nativeHistogramMaxZeroThreshold float64
|
|
nativeHistogramMaxBuckets uint32
|
|
nativeHistogramMaxBuckets uint32
|
|
nativeHistogramMinResetDuration time.Duration
|
|
nativeHistogramMinResetDuration time.Duration
|
|
- lastResetTime time.Time // Protected by mtx.
|
|
|
|
|
|
+ // lastResetTime is protected by mtx. It is also used as created timestamp.
|
|
|
|
+ lastResetTime time.Time
|
|
|
|
|
|
- now func() time.Time // To mock out time.Now() for testing.
|
|
|
|
|
|
+ // now is for testing purposes, by default it's time.Now.
|
|
|
|
+ now func() time.Time
|
|
}
|
|
}
|
|
|
|
|
|
func (h *histogram) Desc() *Desc {
|
|
func (h *histogram) Desc() *Desc {
|
|
@@ -735,9 +757,10 @@ func (h *histogram) Write(out *dto.Metric) error {
|
|
waitForCooldown(count, coldCounts)
|
|
waitForCooldown(count, coldCounts)
|
|
|
|
|
|
his := &dto.Histogram{
|
|
his := &dto.Histogram{
|
|
- Bucket: make([]*dto.Bucket, len(h.upperBounds)),
|
|
|
|
- SampleCount: proto.Uint64(count),
|
|
|
|
- SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
|
|
|
|
|
|
+ Bucket: make([]*dto.Bucket, len(h.upperBounds)),
|
|
|
|
+ SampleCount: proto.Uint64(count),
|
|
|
|
+ SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
|
|
|
|
+ CreatedTimestamp: timestamppb.New(h.lastResetTime),
|
|
}
|
|
}
|
|
out.Histogram = his
|
|
out.Histogram = his
|
|
out.Label = h.labelPairs
|
|
out.Label = h.labelPairs
|
|
@@ -775,6 +798,16 @@ func (h *histogram) Write(out *dto.Metric) error {
|
|
his.ZeroCount = proto.Uint64(zeroBucket)
|
|
his.ZeroCount = proto.Uint64(zeroBucket)
|
|
his.NegativeSpan, his.NegativeDelta = makeBuckets(&coldCounts.nativeHistogramBucketsNegative)
|
|
his.NegativeSpan, his.NegativeDelta = makeBuckets(&coldCounts.nativeHistogramBucketsNegative)
|
|
his.PositiveSpan, his.PositiveDelta = makeBuckets(&coldCounts.nativeHistogramBucketsPositive)
|
|
his.PositiveSpan, his.PositiveDelta = makeBuckets(&coldCounts.nativeHistogramBucketsPositive)
|
|
|
|
+
|
|
|
|
+ // Add a no-op span to a histogram without observations and with
|
|
|
|
+ // a zero threshold of zero. Otherwise, a native histogram would
|
|
|
|
+ // look like a classic histogram to scrapers.
|
|
|
|
+ if *his.ZeroThreshold == 0 && *his.ZeroCount == 0 && len(his.PositiveSpan) == 0 && len(his.NegativeSpan) == 0 {
|
|
|
|
+ his.PositiveSpan = []*dto.BucketSpan{{
|
|
|
|
+ Offset: proto.Int32(0),
|
|
|
|
+ Length: proto.Uint32(0),
|
|
|
|
+ }}
|
|
|
|
+ }
|
|
}
|
|
}
|
|
addAndResetCounts(hotCounts, coldCounts)
|
|
addAndResetCounts(hotCounts, coldCounts)
|
|
return nil
|
|
return nil
|
|
@@ -810,7 +843,7 @@ func (h *histogram) observe(v float64, bucket int) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-// limitSparsebuckets applies a strategy to limit the number of populated sparse
|
|
|
|
|
|
+// limitBuckets applies a strategy to limit the number of populated sparse
|
|
// buckets. It's generally best effort, and there are situations where the
|
|
// buckets. It's generally best effort, and there are situations where the
|
|
// number can go higher (if even the lowest resolution isn't enough to reduce
|
|
// number can go higher (if even the lowest resolution isn't enough to reduce
|
|
// the number sufficiently, or if the provided counts aren't fully updated yet
|
|
// the number sufficiently, or if the provided counts aren't fully updated yet
|
|
@@ -847,20 +880,23 @@ func (h *histogram) limitBuckets(counts *histogramCounts, value float64, bucket
|
|
h.doubleBucketWidth(hotCounts, coldCounts)
|
|
h.doubleBucketWidth(hotCounts, coldCounts)
|
|
}
|
|
}
|
|
|
|
|
|
-// maybeReset resests the whole histogram if at least h.nativeHistogramMinResetDuration
|
|
|
|
|
|
+// maybeReset resets the whole histogram if at least h.nativeHistogramMinResetDuration
|
|
// has been passed. It returns true if the histogram has been reset. The caller
|
|
// has been passed. It returns true if the histogram has been reset. The caller
|
|
// must have locked h.mtx.
|
|
// must have locked h.mtx.
|
|
-func (h *histogram) maybeReset(hot, cold *histogramCounts, coldIdx uint64, value float64, bucket int) bool {
|
|
|
|
|
|
+func (h *histogram) maybeReset(
|
|
|
|
+ hot, cold *histogramCounts, coldIdx uint64, value float64, bucket int,
|
|
|
|
+) bool {
|
|
// We are using the possibly mocked h.now() rather than
|
|
// We are using the possibly mocked h.now() rather than
|
|
// time.Since(h.lastResetTime) to enable testing.
|
|
// time.Since(h.lastResetTime) to enable testing.
|
|
- if h.nativeHistogramMinResetDuration == 0 || h.now().Sub(h.lastResetTime) < h.nativeHistogramMinResetDuration {
|
|
|
|
|
|
+ if h.nativeHistogramMinResetDuration == 0 ||
|
|
|
|
+ h.now().Sub(h.lastResetTime) < h.nativeHistogramMinResetDuration {
|
|
return false
|
|
return false
|
|
}
|
|
}
|
|
// Completely reset coldCounts.
|
|
// Completely reset coldCounts.
|
|
h.resetCounts(cold)
|
|
h.resetCounts(cold)
|
|
// Repeat the latest observation to not lose it completely.
|
|
// Repeat the latest observation to not lose it completely.
|
|
cold.observe(value, bucket, true)
|
|
cold.observe(value, bucket, true)
|
|
- // Make coldCounts the new hot counts while ressetting countAndHotIdx.
|
|
|
|
|
|
+ // Make coldCounts the new hot counts while resetting countAndHotIdx.
|
|
n := atomic.SwapUint64(&h.countAndHotIdx, (coldIdx<<63)+1)
|
|
n := atomic.SwapUint64(&h.countAndHotIdx, (coldIdx<<63)+1)
|
|
count := n & ((1 << 63) - 1)
|
|
count := n & ((1 << 63) - 1)
|
|
waitForCooldown(count, hot)
|
|
waitForCooldown(count, hot)
|
|
@@ -1034,15 +1070,23 @@ type HistogramVec struct {
|
|
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
|
|
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
|
|
// partitioned by the given label names.
|
|
// partitioned by the given label names.
|
|
func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
|
|
func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
|
|
- desc := NewDesc(
|
|
|
|
|
|
+ return V2.NewHistogramVec(HistogramVecOpts{
|
|
|
|
+ HistogramOpts: opts,
|
|
|
|
+ VariableLabels: UnconstrainedLabels(labelNames),
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// NewHistogramVec creates a new HistogramVec based on the provided HistogramVecOpts.
|
|
|
|
+func (v2) NewHistogramVec(opts HistogramVecOpts) *HistogramVec {
|
|
|
|
+ desc := V2.NewDesc(
|
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
opts.Help,
|
|
opts.Help,
|
|
- labelNames,
|
|
|
|
|
|
+ opts.VariableLabels,
|
|
opts.ConstLabels,
|
|
opts.ConstLabels,
|
|
)
|
|
)
|
|
return &HistogramVec{
|
|
return &HistogramVec{
|
|
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
|
|
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
|
|
- return newHistogram(desc, opts, lvs...)
|
|
|
|
|
|
+ return newHistogram(desc, opts.HistogramOpts, lvs...)
|
|
}),
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1161,6 +1205,7 @@ type constHistogram struct {
|
|
sum float64
|
|
sum float64
|
|
buckets map[float64]uint64
|
|
buckets map[float64]uint64
|
|
labelPairs []*dto.LabelPair
|
|
labelPairs []*dto.LabelPair
|
|
|
|
+ createdTs *timestamppb.Timestamp
|
|
}
|
|
}
|
|
|
|
|
|
func (h *constHistogram) Desc() *Desc {
|
|
func (h *constHistogram) Desc() *Desc {
|
|
@@ -1168,7 +1213,9 @@ func (h *constHistogram) Desc() *Desc {
|
|
}
|
|
}
|
|
|
|
|
|
func (h *constHistogram) Write(out *dto.Metric) error {
|
|
func (h *constHistogram) Write(out *dto.Metric) error {
|
|
- his := &dto.Histogram{}
|
|
|
|
|
|
+ his := &dto.Histogram{
|
|
|
|
+ CreatedTimestamp: h.createdTs,
|
|
|
|
+ }
|
|
|
|
|
|
buckets := make([]*dto.Bucket, 0, len(h.buckets))
|
|
buckets := make([]*dto.Bucket, 0, len(h.buckets))
|
|
|
|
|
|
@@ -1215,7 +1262,7 @@ func NewConstHistogram(
|
|
if desc.err != nil {
|
|
if desc.err != nil {
|
|
return nil, desc.err
|
|
return nil, desc.err
|
|
}
|
|
}
|
|
- if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
|
|
|
|
|
|
+ if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
return &constHistogram{
|
|
return &constHistogram{
|
|
@@ -1309,7 +1356,7 @@ func makeBuckets(buckets *sync.Map) ([]*dto.BucketSpan, []int64) {
|
|
// Multiple spans with only small gaps in between are probably
|
|
// Multiple spans with only small gaps in between are probably
|
|
// encoded more efficiently as one larger span with a few empty
|
|
// encoded more efficiently as one larger span with a few empty
|
|
// buckets. Needs some research to find the sweet spot. For now,
|
|
// buckets. Needs some research to find the sweet spot. For now,
|
|
- // we assume that gaps of one ore two buckets should not create
|
|
|
|
|
|
+ // we assume that gaps of one or two buckets should not create
|
|
// a new span.
|
|
// a new span.
|
|
iDelta := int32(i - nextI)
|
|
iDelta := int32(i - nextI)
|
|
if n == 0 || iDelta > 2 {
|
|
if n == 0 || iDelta > 2 {
|