[chore]: Bump github.com/prometheus/client_golang from 1.20.5 to 1.21.0 (#3860)

Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.5 to 1.21.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.20.5...v1.21.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
dependabot[bot] 2025-03-03 10:21:50 +00:00 committed by GitHub
parent 861b5cd920
commit ddd9210614
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 750 additions and 150 deletions

4
go.mod
View file

@ -55,7 +55,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/ncruces/go-sqlite3 v0.24.0 github.com/ncruces/go-sqlite3 v0.24.0
github.com/oklog/ulid v1.3.1 github.com/oklog/ulid v1.3.1
github.com/prometheus/client_golang v1.20.5 github.com/prometheus/client_golang v1.21.0
github.com/rivo/uniseg v0.4.7 github.com/rivo/uniseg v0.4.7
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.19.0 github.com/spf13/viper v1.19.0
@ -184,7 +184,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.61.0 // indirect github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/puzpuzpuz/xsync/v3 v3.5.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.0 // indirect
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect

8
go.sum generated
View file

@ -343,12 +343,12 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4= github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4=

View file

@ -0,0 +1,50 @@
// Copyright 2014 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 (
"math"
"sync/atomic"
"time"
)
// atomicUpdateFloat atomically updates the float64 value pointed to by bits
// using the provided updateFunc, with an exponential backoff on contention.
func atomicUpdateFloat(bits *uint64, updateFunc func(float64) float64) {
const (
// both numbers are derived from empirical observations
// documented in this PR: https://github.com/prometheus/client_golang/pull/1661
maxBackoff = 320 * time.Millisecond
initialBackoff = 10 * time.Millisecond
)
backoff := initialBackoff
for {
loadedBits := atomic.LoadUint64(bits)
oldFloat := math.Float64frombits(loadedBits)
newFloat := updateFunc(oldFloat)
newBits := math.Float64bits(newFloat)
if atomic.CompareAndSwapUint64(bits, loadedBits, newBits) {
break
} else {
// Exponential backoff with sleep and cap to avoid infinite wait
time.Sleep(backoff)
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
}
}
}

View file

@ -134,13 +134,9 @@ func (c *counter) Add(v float64) {
return return
} }
for { atomicUpdateFloat(&c.valBits, func(oldVal float64) float64 {
oldBits := atomic.LoadUint64(&c.valBits) return oldVal + v
newBits := math.Float64bits(math.Float64frombits(oldBits) + v) })
if atomic.CompareAndSwapUint64(&c.valBits, oldBits, newBits) {
return
}
}
} }
func (c *counter) AddWithExemplar(v float64, e Labels) { func (c *counter) AddWithExemplar(v float64, e Labels) {

View file

@ -189,12 +189,15 @@ func (d *Desc) String() string {
fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()), fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()),
) )
} }
vlStrings := make([]string, 0, len(d.variableLabels.names)) vlStrings := []string{}
for _, vl := range d.variableLabels.names { if d.variableLabels != nil {
if fn, ok := d.variableLabels.labelConstraints[vl]; ok && fn != nil { vlStrings = make([]string, 0, len(d.variableLabels.names))
vlStrings = append(vlStrings, fmt.Sprintf("c(%s)", vl)) for _, vl := range d.variableLabels.names {
} else { if fn, ok := d.variableLabels.labelConstraints[vl]; ok && fn != nil {
vlStrings = append(vlStrings, vl) vlStrings = append(vlStrings, fmt.Sprintf("c(%s)", vl))
} else {
vlStrings = append(vlStrings, vl)
}
} }
} }
return fmt.Sprintf( return fmt.Sprintf(

View file

@ -120,13 +120,9 @@ func (g *gauge) Dec() {
} }
func (g *gauge) Add(val float64) { func (g *gauge) Add(val float64) {
for { atomicUpdateFloat(&g.valBits, func(oldVal float64) float64 {
oldBits := atomic.LoadUint64(&g.valBits) return oldVal + val
newBits := math.Float64bits(math.Float64frombits(oldBits) + val) })
if atomic.CompareAndSwapUint64(&g.valBits, oldBits, newBits) {
return
}
}
} }
func (g *gauge) Sub(val float64) { func (g *gauge) Sub(val float64) {

View file

@ -288,7 +288,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
} }
func attachOriginalName(desc, origName string) string { func attachOriginalName(desc, origName string) string {
return fmt.Sprintf("%s Sourced from %s", desc, origName) return fmt.Sprintf("%s Sourced from %s.", desc, origName)
} }
// Describe returns all descriptions of the collector. // Describe returns all descriptions of the collector.

View file

@ -14,6 +14,7 @@
package prometheus package prometheus
import ( import (
"errors"
"fmt" "fmt"
"math" "math"
"runtime" "runtime"
@ -28,6 +29,11 @@
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
) )
const (
nativeHistogramSchemaMaximum = 8
nativeHistogramSchemaMinimum = -4
)
// nativeHistogramBounds for the frac of observed values. Only relevant for // nativeHistogramBounds for the frac of observed values. Only relevant for
// schema > 0. The position in the slice is the schema. (0 is never used, just // schema > 0. The position in the slice is the schema. (0 is never used, just
// here for convenience of using the schema directly as the index.) // here for convenience of using the schema directly as the index.)
@ -330,11 +336,11 @@ func ExponentialBuckets(start, factor float64, count int) []float64 {
// used for the Buckets field of HistogramOpts. // used for the Buckets field of HistogramOpts.
// //
// The function panics if 'count' is 0 or negative, if 'min' is 0 or negative. // The function panics if 'count' is 0 or negative, if 'min' is 0 or negative.
func ExponentialBucketsRange(min, max float64, count int) []float64 { func ExponentialBucketsRange(minBucket, maxBucket float64, count int) []float64 {
if count < 1 { if count < 1 {
panic("ExponentialBucketsRange count needs a positive count") panic("ExponentialBucketsRange count needs a positive count")
} }
if min <= 0 { if minBucket <= 0 {
panic("ExponentialBucketsRange min needs to be greater than 0") panic("ExponentialBucketsRange min needs to be greater than 0")
} }
@ -342,12 +348,12 @@ func ExponentialBucketsRange(min, max float64, count int) []float64 {
// max = min*growthFactor^(bucketCount-1) // max = min*growthFactor^(bucketCount-1)
// We know max/min and highest bucket. Solve for growthFactor. // We know max/min and highest bucket. Solve for growthFactor.
growthFactor := math.Pow(max/min, 1.0/float64(count-1)) growthFactor := math.Pow(maxBucket/minBucket, 1.0/float64(count-1))
// Now that we know growthFactor, solve for each bucket. // Now that we know growthFactor, solve for each bucket.
buckets := make([]float64, count) buckets := make([]float64, count)
for i := 1; i <= count; i++ { for i := 1; i <= count; i++ {
buckets[i-1] = min * math.Pow(growthFactor, float64(i-1)) buckets[i-1] = minBucket * math.Pow(growthFactor, float64(i-1))
} }
return buckets return buckets
} }
@ -858,15 +864,35 @@ func (h *histogram) Write(out *dto.Metric) error {
// findBucket returns the index of the bucket for the provided value, or // findBucket returns the index of the bucket for the provided value, or
// len(h.upperBounds) for the +Inf bucket. // len(h.upperBounds) for the +Inf bucket.
func (h *histogram) findBucket(v float64) int { func (h *histogram) findBucket(v float64) int {
// TODO(beorn7): For small numbers of buckets (<30), a linear search is n := len(h.upperBounds)
// slightly faster than the binary search. If we really care, we could if n == 0 {
// switch from one search strategy to the other depending on the number return 0
// of buckets. }
//
// Microbenchmarks (BenchmarkHistogramNoLabels): // Early exit: if v is less than or equal to the first upper bound, return 0
// 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op if v <= h.upperBounds[0] {
// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op return 0
// 300 buckets: 154 ns/op linear - binary 61.6 ns/op }
// Early exit: if v is greater than the last upper bound, return len(h.upperBounds)
if v > h.upperBounds[n-1] {
return n
}
// For small arrays, use simple linear search
// "magic number" 35 is result of tests on couple different (AWS and baremetal) servers
// see more details here: https://github.com/prometheus/client_golang/pull/1662
if n < 35 {
for i, bound := range h.upperBounds {
if v <= bound {
return i
}
}
// If v is greater than all upper bounds, return len(h.upperBounds)
return n
}
// For larger arrays, use stdlib's binary search
return sort.SearchFloat64s(h.upperBounds, v) return sort.SearchFloat64s(h.upperBounds, v)
} }
@ -1440,9 +1466,9 @@ func pickSchema(bucketFactor float64) int32 {
floor := math.Floor(math.Log2(math.Log2(bucketFactor))) floor := math.Floor(math.Log2(math.Log2(bucketFactor)))
switch { switch {
case floor <= -8: case floor <= -8:
return 8 return nativeHistogramSchemaMaximum
case floor >= 4: case floor >= 4:
return -4 return nativeHistogramSchemaMinimum
default: default:
return -int32(floor) return -int32(floor)
} }
@ -1621,13 +1647,9 @@ func waitForCooldown(count uint64, counts *histogramCounts) {
// atomicAddFloat adds the provided float atomically to another float // atomicAddFloat adds the provided float atomically to another float
// represented by the bit pattern the bits pointer is pointing to. // represented by the bit pattern the bits pointer is pointing to.
func atomicAddFloat(bits *uint64, v float64) { func atomicAddFloat(bits *uint64, v float64) {
for { atomicUpdateFloat(bits, func(oldVal float64) float64 {
loadedBits := atomic.LoadUint64(bits) return oldVal + v
newBits := math.Float64bits(math.Float64frombits(loadedBits) + v) })
if atomic.CompareAndSwapUint64(bits, loadedBits, newBits) {
break
}
}
} }
// atomicDecUint32 atomically decrements the uint32 p points to. See // atomicDecUint32 atomically decrements the uint32 p points to. See
@ -1835,3 +1857,196 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
n.exemplars = append(n.exemplars[:nIdx], append([]*dto.Exemplar{e}, append(n.exemplars[nIdx:rIdx], n.exemplars[rIdx+1:]...)...)...) n.exemplars = append(n.exemplars[:nIdx], append([]*dto.Exemplar{e}, append(n.exemplars[nIdx:rIdx], n.exemplars[rIdx+1:]...)...)...)
} }
} }
type constNativeHistogram struct {
desc *Desc
dto.Histogram
labelPairs []*dto.LabelPair
}
func validateCount(sum float64, count uint64, negativeBuckets, positiveBuckets map[int]int64, zeroBucket uint64) error {
var bucketPopulationSum int64
for _, v := range positiveBuckets {
bucketPopulationSum += v
}
for _, v := range negativeBuckets {
bucketPopulationSum += v
}
bucketPopulationSum += int64(zeroBucket)
// If the sum of observations is NaN, the number of observations must be greater or equal to the sum of all bucket counts.
// Otherwise, the number of observations must be equal to the sum of all bucket counts .
if math.IsNaN(sum) && bucketPopulationSum > int64(count) ||
!math.IsNaN(sum) && bucketPopulationSum != int64(count) {
return errors.New("the sum of all bucket populations exceeds the count of observations")
}
return nil
}
// NewConstNativeHistogram returns a metric representing a Prometheus native histogram with
// fixed values for the count, sum, and positive/negative/zero 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
// OpenTelemetry Collectors, it is useful as a throw-away metric that is generated on the fly
// to send it to Prometheus in the Collect method.
//
// zeroBucket counts all (positive and negative)
// observations in the zero bucket (with an absolute value less or equal
// the current threshold).
// positiveBuckets and negativeBuckets are separate maps for negative and positive
// observations. The map's value is an int64, counting observations in
// that bucket. The map's key is the
// index of the bucket according to the used
// Schema. Index 0 is for an upper bound of 1 in positive buckets and for a lower bound of -1 in negative buckets.
// NewConstNativeHistogram returns an error if
// - the length of labelValues is not consistent with the variable labels in Desc or if Desc is invalid.
// - the schema passed is not between 8 and -4
// - the sum of counts in all buckets including the zero bucket does not equal the count if sum is not NaN (or exceeds the count if sum is NaN)
//
// See https://opentelemetry.io/docs/specs/otel/compatibility/prometheus_and_openmetrics/#exponential-histograms for more details about the conversion from OTel to Prometheus.
func NewConstNativeHistogram(
desc *Desc,
count uint64,
sum float64,
positiveBuckets, negativeBuckets map[int]int64,
zeroBucket uint64,
schema int32,
zeroThreshold float64,
createdTimestamp time.Time,
labelValues ...string,
) (Metric, error) {
if desc.err != nil {
return nil, desc.err
}
if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
return nil, err
}
if schema > nativeHistogramSchemaMaximum || schema < nativeHistogramSchemaMinimum {
return nil, errors.New("invalid native histogram schema")
}
if err := validateCount(sum, count, negativeBuckets, positiveBuckets, zeroBucket); err != nil {
return nil, err
}
NegativeSpan, NegativeDelta := makeBucketsFromMap(negativeBuckets)
PositiveSpan, PositiveDelta := makeBucketsFromMap(positiveBuckets)
ret := &constNativeHistogram{
desc: desc,
Histogram: dto.Histogram{
CreatedTimestamp: timestamppb.New(createdTimestamp),
Schema: &schema,
ZeroThreshold: &zeroThreshold,
SampleCount: &count,
SampleSum: &sum,
NegativeSpan: NegativeSpan,
NegativeDelta: NegativeDelta,
PositiveSpan: PositiveSpan,
PositiveDelta: PositiveDelta,
ZeroCount: proto.Uint64(zeroBucket),
},
labelPairs: MakeLabelPairs(desc, labelValues),
}
if *ret.ZeroThreshold == 0 && *ret.ZeroCount == 0 && len(ret.PositiveSpan) == 0 && len(ret.NegativeSpan) == 0 {
ret.PositiveSpan = []*dto.BucketSpan{{
Offset: proto.Int32(0),
Length: proto.Uint32(0),
}}
}
return ret, nil
}
// MustNewConstNativeHistogram is a version of NewConstNativeHistogram that panics where
// NewConstNativeHistogram would have returned an error.
func MustNewConstNativeHistogram(
desc *Desc,
count uint64,
sum float64,
positiveBuckets, negativeBuckets map[int]int64,
zeroBucket uint64,
nativeHistogramSchema int32,
nativeHistogramZeroThreshold float64,
createdTimestamp time.Time,
labelValues ...string,
) Metric {
nativehistogram, err := NewConstNativeHistogram(desc,
count,
sum,
positiveBuckets,
negativeBuckets,
zeroBucket,
nativeHistogramSchema,
nativeHistogramZeroThreshold,
createdTimestamp,
labelValues...)
if err != nil {
panic(err)
}
return nativehistogram
}
func (h *constNativeHistogram) Desc() *Desc {
return h.desc
}
func (h *constNativeHistogram) Write(out *dto.Metric) error {
out.Histogram = &h.Histogram
out.Label = h.labelPairs
return nil
}
func makeBucketsFromMap(buckets map[int]int64) ([]*dto.BucketSpan, []int64) {
if len(buckets) == 0 {
return nil, nil
}
var ii []int
for k := range buckets {
ii = append(ii, k)
}
sort.Ints(ii)
var (
spans []*dto.BucketSpan
deltas []int64
prevCount int64
nextI int
)
appendDelta := func(count int64) {
*spans[len(spans)-1].Length++
deltas = append(deltas, count-prevCount)
prevCount = count
}
for n, i := range ii {
count := buckets[i]
// Multiple spans with only small gaps in between are probably
// encoded more efficiently as one larger span with a few empty
// buckets. Needs some research to find the sweet spot. For now,
// we assume that gaps of one or two buckets should not create
// a new span.
iDelta := int32(i - nextI)
if n == 0 || iDelta > 2 {
// We have to create a new span, either because we are
// at the very beginning, or because we have found a gap
// of more than two buckets.
spans = append(spans, &dto.BucketSpan{
Offset: proto.Int32(iDelta),
Length: proto.Uint32(0),
})
} else {
// We have found a small gap (or no gap at all).
// Insert empty buckets as needed.
for j := int32(0); j < iDelta; j++ {
appendDelta(0)
}
}
appendDelta(count)
nextI = i + 1
}
return spans, deltas
}

View file

@ -22,17 +22,18 @@
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"strconv"
"strings" "strings"
) )
func min(a, b int) int { func minInt(a, b int) int {
if a < b { if a < b {
return a return a
} }
return b return b
} }
func max(a, b int) int { func maxInt(a, b int) int {
if a > b { if a > b {
return a return a
} }
@ -427,12 +428,12 @@ func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
if codes[0].Tag == 'e' { if codes[0].Tag == 'e' {
c := codes[0] c := codes[0]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2} codes[0] = OpCode{c.Tag, maxInt(i1, i2-n), i2, maxInt(j1, j2-n), j2}
} }
if codes[len(codes)-1].Tag == 'e' { if codes[len(codes)-1].Tag == 'e' {
c := codes[len(codes)-1] c := codes[len(codes)-1]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)} codes[len(codes)-1] = OpCode{c.Tag, i1, minInt(i2, i1+n), j1, minInt(j2, j1+n)}
} }
nn := n + n nn := n + n
groups := [][]OpCode{} groups := [][]OpCode{}
@ -443,12 +444,12 @@ func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
// there is a large range with no changes. // there is a large range with no changes.
if c.Tag == 'e' && i2-i1 > nn { if c.Tag == 'e' && i2-i1 > nn {
group = append(group, OpCode{ group = append(group, OpCode{
c.Tag, i1, min(i2, i1+n), c.Tag, i1, minInt(i2, i1+n),
j1, min(j2, j1+n), j1, minInt(j2, j1+n),
}) })
groups = append(groups, group) groups = append(groups, group)
group = []OpCode{} group = []OpCode{}
i1, j1 = max(i1, i2-n), max(j1, j2-n) i1, j1 = maxInt(i1, i2-n), maxInt(j1, j2-n)
} }
group = append(group, OpCode{c.Tag, i1, i2, j1, j2}) group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
} }
@ -515,7 +516,7 @@ func (m *SequenceMatcher) QuickRatio() float64 {
// is faster to compute than either .Ratio() or .QuickRatio(). // is faster to compute than either .Ratio() or .QuickRatio().
func (m *SequenceMatcher) RealQuickRatio() float64 { func (m *SequenceMatcher) RealQuickRatio() float64 {
la, lb := len(m.a), len(m.b) la, lb := len(m.a), len(m.b)
return calculateRatio(min(la, lb), la+lb) return calculateRatio(minInt(la, lb), la+lb)
} }
// Convert range to the "ed" format // Convert range to the "ed" format
@ -524,7 +525,7 @@ func formatRangeUnified(start, stop int) string {
beginning := start + 1 // lines start numbering with one beginning := start + 1 // lines start numbering with one
length := stop - start length := stop - start
if length == 1 { if length == 1 {
return fmt.Sprintf("%d", beginning) return strconv.Itoa(beginning)
} }
if length == 0 { if length == 0 {
beginning-- // empty ranges begin at line just before the range beginning-- // empty ranges begin at line just before the range

View file

@ -66,7 +66,8 @@ func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool)
name += "_total" name += "_total"
} }
valid := model.IsValidMetricName(model.LabelValue(namespace + "_" + subsystem + "_" + name)) // Our current conversion moves to legacy naming, so use legacy validation.
valid := model.IsValidLegacyMetricName(namespace + "_" + subsystem + "_" + name)
switch d.Kind { switch d.Kind {
case metrics.KindUint64: case metrics.KindUint64:
case metrics.KindFloat64: case metrics.KindFloat64:

View file

@ -108,15 +108,23 @@ func BuildFQName(namespace, subsystem, name string) string {
if name == "" { if name == "" {
return "" return ""
} }
switch {
case namespace != "" && subsystem != "": sb := strings.Builder{}
return strings.Join([]string{namespace, subsystem, name}, "_") sb.Grow(len(namespace) + len(subsystem) + len(name) + 2)
case namespace != "":
return strings.Join([]string{namespace, name}, "_") if namespace != "" {
case subsystem != "": sb.WriteString(namespace)
return strings.Join([]string{subsystem, name}, "_") sb.WriteString("_")
} }
return name
if subsystem != "" {
sb.WriteString(subsystem)
sb.WriteString("_")
}
sb.WriteString(name)
return sb.String()
} }
type invalidMetric struct { type invalidMetric struct {

View file

@ -23,6 +23,7 @@
type processCollector struct { type processCollector struct {
collectFn func(chan<- Metric) collectFn func(chan<- Metric)
describeFn func(chan<- *Desc)
pidFn func() (int, error) pidFn func() (int, error)
reportErrors bool reportErrors bool
cpuTotal *Desc cpuTotal *Desc
@ -122,26 +123,23 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector {
// Set up process metric collection if supported by the runtime. // Set up process metric collection if supported by the runtime.
if canCollectProcess() { if canCollectProcess() {
c.collectFn = c.processCollect c.collectFn = c.processCollect
c.describeFn = c.describe
} else { } else {
c.collectFn = func(ch chan<- Metric) { c.collectFn = c.errorCollectFn
c.reportError(ch, nil, errors.New("process metrics not supported on this platform")) c.describeFn = c.errorDescribeFn
}
} }
return c return c
} }
// Describe returns all descriptions of the collector. func (c *processCollector) errorCollectFn(ch chan<- Metric) {
func (c *processCollector) Describe(ch chan<- *Desc) { c.reportError(ch, nil, errors.New("process metrics not supported on this platform"))
ch <- c.cpuTotal }
ch <- c.openFDs
ch <- c.maxFDs func (c *processCollector) errorDescribeFn(ch chan<- *Desc) {
ch <- c.vsize if c.reportErrors {
ch <- c.maxVsize ch <- NewInvalidDesc(errors.New("process metrics not supported on this platform"))
ch <- c.rss }
ch <- c.startTime
ch <- c.inBytes
ch <- c.outBytes
} }
// Collect returns the current state of all metrics of the collector. // Collect returns the current state of all metrics of the collector.
@ -149,6 +147,11 @@ func (c *processCollector) Collect(ch chan<- Metric) {
c.collectFn(ch) c.collectFn(ch)
} }
// Describe returns all descriptions of the collector.
func (c *processCollector) Describe(ch chan<- *Desc) {
c.describeFn(ch)
}
func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error) { func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error) {
if !c.reportErrors { if !c.reportErrors {
return return

View file

@ -0,0 +1,84 @@
// Copyright 2024 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.
//go:build darwin && cgo
#include <mach/mach_init.h>
#include <mach/task.h>
#include <mach/mach_vm.h>
// The compiler warns that mach/shared_memory_server.h is deprecated, and to use
// mach/shared_region.h instead. But that doesn't define
// SHARED_DATA_REGION_SIZE or SHARED_TEXT_REGION_SIZE, so redefine them here and
// avoid a warning message when running tests.
#define GLOBAL_SHARED_TEXT_SEGMENT 0x90000000U
#define SHARED_DATA_REGION_SIZE 0x10000000
#define SHARED_TEXT_REGION_SIZE 0x10000000
int get_memory_info(unsigned long long *rss, unsigned long long *vsize)
{
// This is lightly adapted from how ps(1) obtains its memory info.
// https://github.com/apple-oss-distributions/adv_cmds/blob/8744084ea0ff41ca4bb96b0f9c22407d0e48e9b7/ps/tasks.c#L109
kern_return_t error;
task_t task = MACH_PORT_NULL;
mach_task_basic_info_data_t info;
mach_msg_type_number_t info_count = MACH_TASK_BASIC_INFO_COUNT;
error = task_info(
mach_task_self(),
MACH_TASK_BASIC_INFO,
(task_info_t) &info,
&info_count );
if( error != KERN_SUCCESS )
{
return error;
}
*rss = info.resident_size;
*vsize = info.virtual_size;
{
vm_region_basic_info_data_64_t b_info;
mach_vm_address_t address = GLOBAL_SHARED_TEXT_SEGMENT;
mach_vm_size_t size;
mach_port_t object_name;
/*
* try to determine if this task has the split libraries
* mapped in... if so, adjust its virtual size down by
* the 2 segments that are used for split libraries
*/
info_count = VM_REGION_BASIC_INFO_COUNT_64;
error = mach_vm_region(
mach_task_self(),
&address,
&size,
VM_REGION_BASIC_INFO_64,
(vm_region_info_t) &b_info,
&info_count,
&object_name);
if (error == KERN_SUCCESS) {
if (b_info.reserved && size == (SHARED_TEXT_REGION_SIZE) &&
*vsize > (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE)) {
*vsize -= (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE);
}
}
}
return 0;
}

View file

@ -0,0 +1,51 @@
// Copyright 2024 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.
//go:build darwin && cgo
package prometheus
/*
int get_memory_info(unsigned long long *rss, unsigned long long *vs);
*/
import "C"
import "fmt"
func getMemory() (*memoryInfo, error) {
var rss, vsize C.ulonglong
if err := C.get_memory_info(&rss, &vsize); err != 0 {
return nil, fmt.Errorf("task_info() failed with 0x%x", int(err))
}
return &memoryInfo{vsize: uint64(vsize), rss: uint64(rss)}, nil
}
// describe returns all descriptions of the collector for Darwin.
// Ensure that this list of descriptors is kept in sync with the metrics collected
// in the processCollect method. Any changes to the metrics in processCollect
// (such as adding or removing metrics) should be reflected in this list of descriptors.
func (c *processCollector) describe(ch chan<- *Desc) {
ch <- c.cpuTotal
ch <- c.openFDs
ch <- c.maxFDs
ch <- c.maxVsize
ch <- c.startTime
ch <- c.rss
ch <- c.vsize
/* the process could be collected but not implemented yet
ch <- c.inBytes
ch <- c.outBytes
*/
}

View file

@ -0,0 +1,128 @@
// Copyright 2024 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 (
"errors"
"fmt"
"os"
"syscall"
"time"
"golang.org/x/sys/unix"
)
// notImplementedErr is returned by stub functions that replace cgo functions, when cgo
// isn't available.
var notImplementedErr = errors.New("not implemented")
type memoryInfo struct {
vsize uint64 // Virtual memory size in bytes
rss uint64 // Resident memory size in bytes
}
func canCollectProcess() bool {
return true
}
func getSoftLimit(which int) (uint64, error) {
rlimit := syscall.Rlimit{}
if err := syscall.Getrlimit(which, &rlimit); err != nil {
return 0, err
}
return rlimit.Cur, nil
}
func getOpenFileCount() (float64, error) {
// Alternately, the undocumented proc_pidinfo(PROC_PIDLISTFDS) can be used to
// return a list of open fds, but that requires a way to call C APIs. The
// benefits, however, include fewer system calls and not failing when at the
// open file soft limit.
if dir, err := os.Open("/dev/fd"); err != nil {
return 0.0, err
} else {
defer dir.Close()
// Avoid ReadDir(), as it calls stat(2) on each descriptor. Not only is
// that info not used, but KQUEUE descriptors fail stat(2), which causes
// the whole method to fail.
if names, err := dir.Readdirnames(0); err != nil {
return 0.0, err
} else {
// Subtract 1 to ignore the open /dev/fd descriptor above.
return float64(len(names) - 1), nil
}
}
}
func (c *processCollector) processCollect(ch chan<- Metric) {
if procs, err := unix.SysctlKinfoProcSlice("kern.proc.pid", os.Getpid()); err == nil {
if len(procs) == 1 {
startTime := float64(procs[0].Proc.P_starttime.Nano() / 1e9)
ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime)
} else {
err = fmt.Errorf("sysctl() returned %d proc structs (expected 1)", len(procs))
c.reportError(ch, c.startTime, err)
}
} else {
c.reportError(ch, c.startTime, err)
}
// The proc structure returned by kern.proc.pid above has an Rusage member,
// but it is not filled in, so it needs to be fetched by getrusage(2). For
// that call, the UTime, STime, and Maxrss members are filled out, but not
// Ixrss, Idrss, or Isrss for the memory usage. Memory stats will require
// access to the C API to call task_info(TASK_BASIC_INFO).
rusage := unix.Rusage{}
if err := unix.Getrusage(syscall.RUSAGE_SELF, &rusage); err == nil {
cpuTime := time.Duration(rusage.Stime.Nano() + rusage.Utime.Nano()).Seconds()
ch <- MustNewConstMetric(c.cpuTotal, CounterValue, cpuTime)
} else {
c.reportError(ch, c.cpuTotal, err)
}
if memInfo, err := getMemory(); err == nil {
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(memInfo.rss))
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(memInfo.vsize))
} else if !errors.Is(err, notImplementedErr) {
// Don't report an error when support is not compiled in.
c.reportError(ch, c.rss, err)
c.reportError(ch, c.vsize, err)
}
if fds, err := getOpenFileCount(); err == nil {
ch <- MustNewConstMetric(c.openFDs, GaugeValue, fds)
} else {
c.reportError(ch, c.openFDs, err)
}
if openFiles, err := getSoftLimit(syscall.RLIMIT_NOFILE); err == nil {
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(openFiles))
} else {
c.reportError(ch, c.maxFDs, err)
}
if addressSpace, err := getSoftLimit(syscall.RLIMIT_AS); err == nil {
ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(addressSpace))
} else {
c.reportError(ch, c.maxVsize, err)
}
// TODO: socket(PF_SYSTEM) to fetch "com.apple.network.statistics" might
// be able to get the per-process network send/receive counts.
}

View file

@ -0,0 +1,39 @@
// Copyright 2024 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.
//go:build darwin && !cgo
package prometheus
func getMemory() (*memoryInfo, error) {
return nil, notImplementedErr
}
// describe returns all descriptions of the collector for Darwin.
// Ensure that this list of descriptors is kept in sync with the metrics collected
// in the processCollect method. Any changes to the metrics in processCollect
// (such as adding or removing metrics) should be reflected in this list of descriptors.
func (c *processCollector) describe(ch chan<- *Desc) {
ch <- c.cpuTotal
ch <- c.openFDs
ch <- c.maxFDs
ch <- c.maxVsize
ch <- c.startTime
/* the process could be collected but not implemented yet
ch <- c.rss
ch <- c.vsize
ch <- c.inBytes
ch <- c.outBytes
*/
}

View file

@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !windows && !js && !wasip1 //go:build !windows && !js && !wasip1 && !darwin
// +build !windows,!js,!wasip1 // +build !windows,!js,!wasip1,!darwin
package prometheus package prometheus
@ -78,3 +78,19 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
c.reportError(ch, nil, err) c.reportError(ch, nil, err)
} }
} }
// describe returns all descriptions of the collector for others than windows, js, wasip1 and darwin.
// Ensure that this list of descriptors is kept in sync with the metrics collected
// in the processCollect method. Any changes to the metrics in processCollect
// (such as adding or removing metrics) should be reflected in this list of descriptors.
func (c *processCollector) describe(ch chan<- *Desc) {
ch <- c.cpuTotal
ch <- c.openFDs
ch <- c.maxFDs
ch <- c.vsize
ch <- c.maxVsize
ch <- c.rss
ch <- c.startTime
ch <- c.inBytes
ch <- c.outBytes
}

View file

@ -1,26 +0,0 @@
// Copyright 2023 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.
//go:build wasip1
// +build wasip1
package prometheus
func canCollectProcess() bool {
return false
}
func (*processCollector) processCollect(chan<- Metric) {
// noop on this platform
return
}

View file

@ -1,4 +1,4 @@
// Copyright 2019 The Prometheus Authors // Copyright 2023 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build js //go:build wasip1 || js
// +build js // +build wasip1 js
package prometheus package prometheus
@ -21,6 +21,13 @@ func canCollectProcess() bool {
} }
func (c *processCollector) processCollect(ch chan<- Metric) { func (c *processCollector) processCollect(ch chan<- Metric) {
// noop on this platform c.errorCollectFn(ch)
return }
// describe returns all descriptions of the collector for wasip1 and js.
// Ensure that this list of descriptors is kept in sync with the metrics collected
// in the processCollect method. Any changes to the metrics in processCollect
// (such as adding or removing metrics) should be reflected in this list of descriptors.
func (c *processCollector) describe(ch chan<- *Desc) {
c.errorDescribeFn(ch)
} }

View file

@ -79,14 +79,10 @@ func getProcessHandleCount(handle windows.Handle) (uint32, error) {
} }
func (c *processCollector) processCollect(ch chan<- Metric) { func (c *processCollector) processCollect(ch chan<- Metric) {
h, err := windows.GetCurrentProcess() h := windows.CurrentProcess()
if err != nil {
c.reportError(ch, nil, err)
return
}
var startTime, exitTime, kernelTime, userTime windows.Filetime var startTime, exitTime, kernelTime, userTime windows.Filetime
err = windows.GetProcessTimes(h, &startTime, &exitTime, &kernelTime, &userTime) err := windows.GetProcessTimes(h, &startTime, &exitTime, &kernelTime, &userTime)
if err != nil { if err != nil {
c.reportError(ch, nil, err) c.reportError(ch, nil, err)
return return
@ -111,6 +107,19 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(16*1024*1024)) // Windows has a hard-coded max limit, not per-process. ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(16*1024*1024)) // Windows has a hard-coded max limit, not per-process.
} }
// describe returns all descriptions of the collector for windows.
// Ensure that this list of descriptors is kept in sync with the metrics collected
// in the processCollect method. Any changes to the metrics in processCollect
// (such as adding or removing metrics) should be reflected in this list of descriptors.
func (c *processCollector) describe(ch chan<- *Desc) {
ch <- c.cpuTotal
ch <- c.openFDs
ch <- c.maxFDs
ch <- c.vsize
ch <- c.rss
ch <- c.startTime
}
func fileTimeToSeconds(ft windows.Filetime) float64 { func fileTimeToSeconds(ft windows.Filetime) float64 {
return float64(uint64(ft.HighDateTime)<<32+uint64(ft.LowDateTime)) / 1e7 return float64(uint64(ft.HighDateTime)<<32+uint64(ft.LowDateTime)) / 1e7
} }

View file

@ -207,7 +207,13 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
if encodingHeader != string(Identity) { if encodingHeader != string(Identity) {
rsp.Header().Set(contentEncodingHeader, encodingHeader) rsp.Header().Set(contentEncodingHeader, encodingHeader)
} }
enc := expfmt.NewEncoder(w, contentType)
var enc expfmt.Encoder
if opts.EnableOpenMetricsTextCreatedSamples {
enc = expfmt.NewEncoder(w, contentType, expfmt.WithCreatedLines())
} else {
enc = expfmt.NewEncoder(w, contentType)
}
// handleError handles the error according to opts.ErrorHandling // handleError handles the error according to opts.ErrorHandling
// and returns true if we have to abort after the handling. // and returns true if we have to abort after the handling.
@ -408,6 +414,21 @@ type HandlerOpts struct {
// (which changes the identity of the resulting series on the Prometheus // (which changes the identity of the resulting series on the Prometheus
// server). // server).
EnableOpenMetrics bool EnableOpenMetrics bool
// EnableOpenMetricsTextCreatedSamples specifies if this handler should add, extra, synthetic
// Created Timestamps for counters, histograms and summaries, which for the current
// version of OpenMetrics are defined as extra series with the same name and "_created"
// suffix. See also the OpenMetrics specification for more details
// https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#counter-1
//
// Created timestamps are used to improve the accuracy of reset detection,
// but the way it's designed in OpenMetrics 1.0 it also dramatically increases cardinality
// if the scraper does not handle those metrics correctly (converting to created timestamp
// instead of leaving those series as-is). New OpenMetrics versions might improve
// this situation.
//
// Prometheus introduced the feature flag 'created-timestamp-zero-ingestion'
// in version 2.50.0 to handle this situation.
EnableOpenMetricsTextCreatedSamples bool
// ProcessStartTime allows setting process start timevalue that will be exposed // ProcessStartTime allows setting process start timevalue that will be exposed
// with "Process-Start-Time-Unix" response header along with the metrics // with "Process-Start-Time-Unix" response header along with the metrics
// payload. This allow callers to have efficient transformations to cumulative // payload. This allow callers to have efficient transformations to cumulative

View file

@ -243,6 +243,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
s := &summary{ s := &summary{
desc: desc, desc: desc,
now: opts.now,
objectives: opts.Objectives, objectives: opts.Objectives,
sortedObjectives: make([]float64, 0, len(opts.Objectives)), sortedObjectives: make([]float64, 0, len(opts.Objectives)),
@ -280,6 +281,8 @@ type summary struct {
desc *Desc desc *Desc
now func() time.Time
objectives map[float64]float64 objectives map[float64]float64
sortedObjectives []float64 sortedObjectives []float64
@ -307,7 +310,7 @@ func (s *summary) Observe(v float64) {
s.bufMtx.Lock() s.bufMtx.Lock()
defer s.bufMtx.Unlock() defer s.bufMtx.Unlock()
now := time.Now() now := s.now()
if now.After(s.hotBufExpTime) { if now.After(s.hotBufExpTime) {
s.asyncFlush(now) s.asyncFlush(now)
} }
@ -326,7 +329,7 @@ func (s *summary) Write(out *dto.Metric) error {
s.bufMtx.Lock() s.bufMtx.Lock()
s.mtx.Lock() s.mtx.Lock()
// Swap bufs even if hotBuf is empty to set new hotBufExpTime. // Swap bufs even if hotBuf is empty to set new hotBufExpTime.
s.swapBufs(time.Now()) s.swapBufs(s.now())
s.bufMtx.Unlock() s.bufMtx.Unlock()
s.flushColdBuf() s.flushColdBuf()
@ -468,13 +471,9 @@ func (s *noObjectivesSummary) Observe(v float64) {
n := atomic.AddUint64(&s.countAndHotIdx, 1) n := atomic.AddUint64(&s.countAndHotIdx, 1)
hotCounts := s.counts[n>>63] hotCounts := s.counts[n>>63]
for { atomicUpdateFloat(&hotCounts.sumBits, func(oldVal float64) float64 {
oldBits := atomic.LoadUint64(&hotCounts.sumBits) return oldVal + v
newBits := math.Float64bits(math.Float64frombits(oldBits) + v) })
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
break
}
}
// Increment count last as we take it as a signal that the observation // Increment count last as we take it as a signal that the observation
// is complete. // is complete.
atomic.AddUint64(&hotCounts.count, 1) atomic.AddUint64(&hotCounts.count, 1)
@ -516,14 +515,13 @@ func (s *noObjectivesSummary) Write(out *dto.Metric) error {
// Finally add all the cold counts to the new hot counts and reset the cold counts. // Finally add all the cold counts to the new hot counts and reset the cold counts.
atomic.AddUint64(&hotCounts.count, count) atomic.AddUint64(&hotCounts.count, count)
atomic.StoreUint64(&coldCounts.count, 0) atomic.StoreUint64(&coldCounts.count, 0)
for {
oldBits := atomic.LoadUint64(&hotCounts.sumBits) // Use atomicUpdateFloat to update hotCounts.sumBits atomically.
newBits := math.Float64bits(math.Float64frombits(oldBits) + sum.GetSampleSum()) atomicUpdateFloat(&hotCounts.sumBits, func(oldVal float64) float64 {
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) { return oldVal + sum.GetSampleSum()
atomic.StoreUint64(&coldCounts.sumBits, 0) })
break atomic.StoreUint64(&coldCounts.sumBits, 0)
}
}
return nil return nil
} }

View file

@ -38,7 +38,7 @@ type encoderOption struct {
// WithCreatedLines is an EncoderOption that configures the OpenMetrics encoder // WithCreatedLines is an EncoderOption that configures the OpenMetrics encoder
// to include _created lines (See // to include _created lines (See
// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1). // https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#counter-1).
// Created timestamps can improve the accuracy of series reset detection, but // Created timestamps can improve the accuracy of series reset detection, but
// come with a bandwidth cost. // come with a bandwidth cost.
// //
@ -102,7 +102,7 @@ func WithUnit() EncoderOption {
// //
// - According to the OM specs, the `# UNIT` line is optional, but if populated, // - According to the OM specs, the `# UNIT` line is optional, but if populated,
// the unit has to be present in the metric name as its suffix: // the unit has to be present in the metric name as its suffix:
// (see https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#unit). // (see https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#unit).
// However, in order to accommodate any potential scenario where such a change in the // However, in order to accommodate any potential scenario where such a change in the
// metric name is not desirable, the users are here given the choice of either explicitly // metric name is not desirable, the users are here given the choice of either explicitly
// opt in, in case they wish for the unit to be included in the output AND in the metric name // opt in, in case they wish for the unit to be included in the output AND in the metric name

View file

@ -28,13 +28,13 @@
var ( var (
// NameValidationScheme determines the method of name validation to be used by // NameValidationScheme determines the method of name validation to be used by
// all calls to IsValidMetricName() and LabelName IsValid(). Setting UTF-8 mode // all calls to IsValidMetricName() and LabelName IsValid(). Setting UTF-8
// in isolation from other components that don't support UTF-8 may result in // mode in isolation from other components that don't support UTF-8 may result
// bugs or other undefined behavior. This value is intended to be set by // in bugs or other undefined behavior. This value can be set to
// UTF-8-aware binaries as part of their startup. To avoid need for locking, // LegacyValidation during startup if a binary is not UTF-8-aware binaries. To
// this value should be set once, ideally in an init(), before multiple // avoid need for locking, this value should be set once, ideally in an
// goroutines are started. // init(), before multiple goroutines are started.
NameValidationScheme = LegacyValidation NameValidationScheme = UTF8Validation
// NameEscapingScheme defines the default way that names will be escaped when // NameEscapingScheme defines the default way that names will be escaped when
// presented to systems that do not support UTF-8 names. If the Content-Type // presented to systems that do not support UTF-8 names. If the Content-Type

6
vendor/modules.txt vendored
View file

@ -735,8 +735,8 @@ github.com/pkg/errors
# github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 # github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
## explicit ## explicit
github.com/pmezard/go-difflib/difflib github.com/pmezard/go-difflib/difflib
# github.com/prometheus/client_golang v1.20.5 # github.com/prometheus/client_golang v1.21.0
## explicit; go 1.20 ## explicit; go 1.21
github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil
github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header
github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus
@ -745,7 +745,7 @@ github.com/prometheus/client_golang/prometheus/promhttp
# github.com/prometheus/client_model v0.6.1 # github.com/prometheus/client_model v0.6.1
## explicit; go 1.19 ## explicit; go 1.19
github.com/prometheus/client_model/go github.com/prometheus/client_model/go
# github.com/prometheus/common v0.61.0 # github.com/prometheus/common v0.62.0
## explicit; go 1.21 ## explicit; go 1.21
github.com/prometheus/common/expfmt github.com/prometheus/common/expfmt
github.com/prometheus/common/model github.com/prometheus/common/model