aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/prometheus/client_golang
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2023-02-22 22:16:50 +0100
committerTaras Madan <tarasmadan@google.com>2023-02-24 12:47:23 +0100
commit4165372ec8fd142475a4e35fd0cf4f8042132208 (patch)
tree21cd62211b4dd80bee469054c5b65db77342333c /vendor/github.com/prometheus/client_golang
parent2b3ed821a493b8936c8bacfa6f8b4f1c90a00855 (diff)
dependencies: update
set go min requirements to 1.19 update dependencies update vendor
Diffstat (limited to 'vendor/github.com/prometheus/client_golang')
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/collector.go6
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/counter.go13
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/desc.go5
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/doc.go107
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/gauge.go6
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/get_pid.go26
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/get_pid_gopherjs.go23
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/go_collector.go20
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/go_collector_go116.go17
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go (renamed from vendor/github.com/prometheus/client_golang/prometheus/go_collector_go117.go)314
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/histogram.go980
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/internal/almost_equal.go60
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go654
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go32
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/internal/go_runtime_metrics.go18
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/internal/metric.go28
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/labels.go9
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/metric.go112
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/num_threads.go25
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/num_threads_gopherjs.go22
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/observer.go2
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/process_collector.go10
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/process_collector_js.go26
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go4
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promauto/auto.go170
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go18
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go20
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go40
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go123
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/option.go39
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/registry.go152
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/summary.go9
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/testutil/promlint/promlint.go5
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/timer.go11
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/value.go47
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/vec.go88
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/wrap.go4
37 files changed, 2781 insertions, 464 deletions
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/collector.go b/vendor/github.com/prometheus/client_golang/prometheus/collector.go
index ac1ca3cf5..cf05079fb 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/collector.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/collector.go
@@ -69,9 +69,9 @@ type Collector interface {
// If a Collector collects the same metrics throughout its lifetime, its
// Describe method can simply be implemented as:
//
-// func (c customCollector) Describe(ch chan<- *Desc) {
-// DescribeByCollect(c, ch)
-// }
+// func (c customCollector) Describe(ch chan<- *Desc) {
+// DescribeByCollect(c, ch)
+// }
//
// However, this will not work if the metrics collected change dynamically over
// the lifetime of the Collector in a way that their combined set of descriptors
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/counter.go b/vendor/github.com/prometheus/client_golang/prometheus/counter.go
index 00d70f09b..a912b75a0 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/counter.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/counter.go
@@ -51,7 +51,7 @@ type Counter interface {
// will lead to a valid (label-less) exemplar. But if Labels is nil, the current
// exemplar is left in place. AddWithExemplar panics if the value is < 0, if any
// of the provided labels are invalid, or if the provided labels contain more
-// than 64 runes in total.
+// than 128 runes in total.
type ExemplarAdder interface {
AddWithExemplar(value float64, exemplar Labels)
}
@@ -140,12 +140,13 @@ func (c *counter) get() float64 {
}
func (c *counter) Write(out *dto.Metric) error {
- val := c.get()
-
+ // Read the Exemplar first and the value second. This is to avoid a race condition
+ // where users see an exemplar for a not-yet-existing observation.
var exemplar *dto.Exemplar
if e := c.exemplar.Load(); e != nil {
exemplar = e.(*dto.Exemplar)
}
+ val := c.get()
return populateMetric(CounterValue, val, c.labelPairs, exemplar, out)
}
@@ -245,7 +246,8 @@ func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
// WithLabelValues works as GetMetricWithLabelValues, but panics where
// GetMetricWithLabelValues would have returned an error. Not returning an
// error allows shortcuts like
-// myVec.WithLabelValues("404", "GET").Add(42)
+//
+// myVec.WithLabelValues("404", "GET").Add(42)
func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
c, err := v.GetMetricWithLabelValues(lvs...)
if err != nil {
@@ -256,7 +258,8 @@ func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
// 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"}).Add(42)
+//
+// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
func (v *CounterVec) With(labels Labels) Counter {
c, err := v.GetMetricWith(labels)
if err != nil {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/desc.go b/vendor/github.com/prometheus/client_golang/prometheus/desc.go
index 4bb816ab7..8bc5e44e2 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/desc.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/desc.go
@@ -20,6 +20,9 @@ import (
"strings"
"github.com/cespare/xxhash/v2"
+
+ "github.com/prometheus/client_golang/prometheus/internal"
+
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto"
"github.com/prometheus/common/model"
@@ -154,7 +157,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
Value: proto.String(v),
})
}
- sort.Sort(labelPairSorter(d.constLabelPairs))
+ sort.Sort(internal.LabelPairSorter(d.constLabelPairs))
return d
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/doc.go b/vendor/github.com/prometheus/client_golang/prometheus/doc.go
index 98450125d..811072cbd 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/doc.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/doc.go
@@ -21,55 +21,66 @@
// All exported functions and methods are safe to be used concurrently unless
// specified otherwise.
//
-// A Basic Example
+// # A Basic Example
//
// As a starting point, a very basic usage example:
//
-// package main
-//
-// import (
-// "log"
-// "net/http"
-//
-// "github.com/prometheus/client_golang/prometheus"
-// "github.com/prometheus/client_golang/prometheus/promhttp"
-// )
-//
-// var (
-// cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{
-// Name: "cpu_temperature_celsius",
-// Help: "Current temperature of the CPU.",
-// })
-// hdFailures = prometheus.NewCounterVec(
-// prometheus.CounterOpts{
-// Name: "hd_errors_total",
-// Help: "Number of hard-disk errors.",
-// },
-// []string{"device"},
-// )
-// )
-//
-// func init() {
-// // Metrics have to be registered to be exposed:
-// prometheus.MustRegister(cpuTemp)
-// prometheus.MustRegister(hdFailures)
-// }
-//
-// func main() {
-// cpuTemp.Set(65.3)
-// hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()
-//
-// // The Handler function provides a default handler to expose metrics
-// // via an HTTP server. "/metrics" is the usual endpoint for that.
-// http.Handle("/metrics", promhttp.Handler())
-// log.Fatal(http.ListenAndServe(":8080", nil))
-// }
-//
+// package main
+//
+// import (
+// "log"
+// "net/http"
+//
+// "github.com/prometheus/client_golang/prometheus"
+// "github.com/prometheus/client_golang/prometheus/promhttp"
+// )
+//
+// type metrics struct {
+// cpuTemp prometheus.Gauge
+// hdFailures *prometheus.CounterVec
+// }
+//
+// func NewMetrics(reg prometheus.Registerer) *metrics {
+// m := &metrics{
+// cpuTemp: prometheus.NewGauge(prometheus.GaugeOpts{
+// Name: "cpu_temperature_celsius",
+// Help: "Current temperature of the CPU.",
+// }),
+// hdFailures: prometheus.NewCounterVec(
+// prometheus.CounterOpts{
+// Name: "hd_errors_total",
+// Help: "Number of hard-disk errors.",
+// },
+// []string{"device"},
+// ),
+// }
+// reg.MustRegister(m.cpuTemp)
+// reg.MustRegister(m.hdFailures)
+// return m
+// }
+//
+// func main() {
+// // Create a non-global registry.
+// reg := prometheus.NewRegistry()
+//
+// // Create new metrics and register them using the custom registry.
+// m := NewMetrics(reg)
+// // Set values for the new created metrics.
+// m.cpuTemp.Set(65.3)
+// m.hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()
+//
+// // Expose metrics and custom registry via an HTTP server
+// // using the HandleFor function. "/metrics" is the usual endpoint for that.
+// http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))
+// log.Fatal(http.ListenAndServe(":8080", nil))
+// }
//
// This is a complete program that exports two metrics, a Gauge and a Counter,
// the latter with a label attached to turn it into a (one-dimensional) vector.
+// It register the metrics using a custom registry and exposes them via an HTTP server
+// on the /metrics endpoint.
//
-// Metrics
+// # Metrics
//
// The number of exported identifiers in this package might appear a bit
// overwhelming. However, in addition to the basic plumbing shown in the example
@@ -100,7 +111,7 @@
// To create instances of Metrics and their vector versions, you need a suitable
// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts, or HistogramOpts.
//
-// Custom Collectors and constant Metrics
+// # Custom Collectors and constant Metrics
//
// While you could create your own implementations of Metric, most likely you
// will only ever implement the Collector interface on your own. At a first
@@ -141,7 +152,7 @@
// a metric, GaugeFunc, CounterFunc, or UntypedFunc might be interesting
// shortcuts.
//
-// Advanced Uses of the Registry
+// # Advanced Uses of the Registry
//
// While MustRegister is the by far most common way of registering a Collector,
// sometimes you might want to handle the errors the registration might cause.
@@ -176,23 +187,23 @@
// NewProcessCollector). With a custom registry, you are in control and decide
// yourself about the Collectors to register.
//
-// HTTP Exposition
+// # HTTP Exposition
//
// The Registry implements the Gatherer interface. The caller of the Gather
// method can then expose the gathered metrics in some way. Usually, the metrics
// are served via HTTP on the /metrics endpoint. That's happening in the example
// above. The tools to expose metrics via HTTP are in the promhttp sub-package.
//
-// Pushing to the Pushgateway
+// # Pushing to the Pushgateway
//
// Function for pushing to the Pushgateway can be found in the push sub-package.
//
-// Graphite Bridge
+// # Graphite Bridge
//
// Functions and examples to push metrics from a Gatherer to Graphite can be
// found in the graphite sub-package.
//
-// Other Means of Exposition
+// # Other Means of Exposition
//
// More ways of exposing metrics can easily be added by following the approaches
// of the existing implementations.
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/gauge.go b/vendor/github.com/prometheus/client_golang/prometheus/gauge.go
index bd0733d6a..21271a5bb 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/gauge.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/gauge.go
@@ -210,7 +210,8 @@ func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
// WithLabelValues works as GetMetricWithLabelValues, but panics where
// GetMetricWithLabelValues would have returned an error. Not returning an
// error allows shortcuts like
-// myVec.WithLabelValues("404", "GET").Add(42)
+//
+// myVec.WithLabelValues("404", "GET").Add(42)
func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
g, err := v.GetMetricWithLabelValues(lvs...)
if err != nil {
@@ -221,7 +222,8 @@ func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
// 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"}).Add(42)
+//
+// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
func (v *GaugeVec) With(labels Labels) Gauge {
g, err := v.GetMetricWith(labels)
if err != nil {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/get_pid.go b/vendor/github.com/prometheus/client_golang/prometheus/get_pid.go
new file mode 100644
index 000000000..614fd61be
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/get_pid.go
@@ -0,0 +1,26 @@
+// 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.
+
+//go:build !js || wasm
+// +build !js wasm
+
+package prometheus
+
+import "os"
+
+func getPIDFn() func() (int, error) {
+ pid := os.Getpid()
+ return func() (int, error) {
+ return pid, nil
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/get_pid_gopherjs.go b/vendor/github.com/prometheus/client_golang/prometheus/get_pid_gopherjs.go
new file mode 100644
index 000000000..eaf8059ee
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/get_pid_gopherjs.go
@@ -0,0 +1,23 @@
+// 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.
+
+//go:build js && !wasm
+// +build js,!wasm
+
+package prometheus
+
+func getPIDFn() func() (int, error) {
+ return func() (int, error) {
+ return 1, nil
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go b/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
index 08195b410..ad9a71a5e 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
@@ -19,6 +19,10 @@ import (
"time"
)
+// goRuntimeMemStats provides the metrics initially provided by runtime.ReadMemStats.
+// From Go 1.17 those similar (and better) statistics are provided by runtime/metrics, so
+// while eval closure works on runtime.MemStats, the struct from Go 1.17+ is
+// populated using runtime/metrics.
func goRuntimeMemStats() memStatsMetrics {
return memStatsMetrics{
{
@@ -197,14 +201,6 @@ func goRuntimeMemStats() memStatsMetrics {
),
eval: func(ms *runtime.MemStats) float64 { return float64(ms.NextGC) },
valType: GaugeValue,
- }, {
- desc: NewDesc(
- memstatNamespace("gc_cpu_fraction"),
- "The fraction of this program's available CPU time used by the GC since the program started.",
- nil, nil,
- ),
- eval: func(ms *runtime.MemStats) float64 { return ms.GCCPUFraction },
- valType: GaugeValue,
},
}
}
@@ -232,7 +228,7 @@ func newBaseGoCollector() baseGoCollector {
"A summary of the pause duration of garbage collection cycles.",
nil, nil),
gcLastTimeDesc: NewDesc(
- memstatNamespace("last_gc_time_seconds"),
+ "go_memstats_last_gc_time_seconds",
"Number of seconds since 1970 of last garbage collection.",
nil, nil),
goInfoDesc: NewDesc(
@@ -254,8 +250,9 @@ func (c *baseGoCollector) Describe(ch chan<- *Desc) {
// Collect returns the current state of all metrics of the collector.
func (c *baseGoCollector) Collect(ch chan<- Metric) {
ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine()))
- n, _ := runtime.ThreadCreateProfile(nil)
- ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, float64(n))
+
+ n := getRuntimeNumThreads()
+ ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, n)
var stats debug.GCStats
stats.PauseQuantiles = make([]time.Duration, 5)
@@ -268,7 +265,6 @@ func (c *baseGoCollector) Collect(ch chan<- Metric) {
quantiles[0.0] = stats.PauseQuantiles[0].Seconds()
ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), stats.PauseTotal.Seconds(), quantiles)
ch <- MustNewConstMetric(c.gcLastTimeDesc, GaugeValue, float64(stats.LastGC.UnixNano())/1e9)
-
ch <- MustNewConstMetric(c.goInfoDesc, GaugeValue, 1)
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_go116.go b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_go116.go
index 24526131e..897a6e906 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_go116.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_go116.go
@@ -40,13 +40,28 @@ type goCollector struct {
//
// Deprecated: Use collectors.NewGoCollector instead.
func NewGoCollector() Collector {
+ msMetrics := goRuntimeMemStats()
+ msMetrics = append(msMetrics, struct {
+ desc *Desc
+ eval func(*runtime.MemStats) float64
+ valType ValueType
+ }{
+ // This metric is omitted in Go1.17+, see https://github.com/prometheus/client_golang/issues/842#issuecomment-861812034
+ desc: NewDesc(
+ memstatNamespace("gc_cpu_fraction"),
+ "The fraction of this program's available CPU time used by the GC since the program started.",
+ nil, nil,
+ ),
+ eval: func(ms *runtime.MemStats) float64 { return ms.GCCPUFraction },
+ valType: GaugeValue,
+ })
return &goCollector{
base: newBaseGoCollector(),
msLast: &runtime.MemStats{},
msRead: runtime.ReadMemStats,
msMaxWait: time.Second,
msMaxAge: 5 * time.Minute,
- msMetrics: goRuntimeMemStats(),
+ msMetrics: msMetrics,
}
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_go117.go b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go
index d43bdcdda..3a2d55e84 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_go117.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go
@@ -25,10 +25,72 @@ import (
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto"
- "github.com/prometheus/client_golang/prometheus/internal"
dto "github.com/prometheus/client_model/go"
+
+ "github.com/prometheus/client_golang/prometheus/internal"
+)
+
+const (
+ // constants for strings referenced more than once.
+ goGCHeapTinyAllocsObjects = "/gc/heap/tiny/allocs:objects"
+ goGCHeapAllocsObjects = "/gc/heap/allocs:objects"
+ goGCHeapFreesObjects = "/gc/heap/frees:objects"
+ goGCHeapFreesBytes = "/gc/heap/frees:bytes"
+ goGCHeapAllocsBytes = "/gc/heap/allocs:bytes"
+ goGCHeapObjects = "/gc/heap/objects:objects"
+ goGCHeapGoalBytes = "/gc/heap/goal:bytes"
+ goMemoryClassesTotalBytes = "/memory/classes/total:bytes"
+ goMemoryClassesHeapObjectsBytes = "/memory/classes/heap/objects:bytes"
+ goMemoryClassesHeapUnusedBytes = "/memory/classes/heap/unused:bytes"
+ goMemoryClassesHeapReleasedBytes = "/memory/classes/heap/released:bytes"
+ goMemoryClassesHeapFreeBytes = "/memory/classes/heap/free:bytes"
+ goMemoryClassesHeapStacksBytes = "/memory/classes/heap/stacks:bytes"
+ goMemoryClassesOSStacksBytes = "/memory/classes/os-stacks:bytes"
+ goMemoryClassesMetadataMSpanInuseBytes = "/memory/classes/metadata/mspan/inuse:bytes"
+ goMemoryClassesMetadataMSPanFreeBytes = "/memory/classes/metadata/mspan/free:bytes"
+ goMemoryClassesMetadataMCacheInuseBytes = "/memory/classes/metadata/mcache/inuse:bytes"
+ goMemoryClassesMetadataMCacheFreeBytes = "/memory/classes/metadata/mcache/free:bytes"
+ goMemoryClassesProfilingBucketsBytes = "/memory/classes/profiling/buckets:bytes"
+ goMemoryClassesMetadataOtherBytes = "/memory/classes/metadata/other:bytes"
+ goMemoryClassesOtherBytes = "/memory/classes/other:bytes"
)
+// rmNamesForMemStatsMetrics represents runtime/metrics names required to populate goRuntimeMemStats from like logic.
+var rmNamesForMemStatsMetrics = []string{
+ goGCHeapTinyAllocsObjects,
+ goGCHeapAllocsObjects,
+ goGCHeapFreesObjects,
+ goGCHeapAllocsBytes,
+ goGCHeapObjects,
+ goGCHeapGoalBytes,
+ goMemoryClassesTotalBytes,
+ goMemoryClassesHeapObjectsBytes,
+ goMemoryClassesHeapUnusedBytes,
+ goMemoryClassesHeapReleasedBytes,
+ goMemoryClassesHeapFreeBytes,
+ goMemoryClassesHeapStacksBytes,
+ goMemoryClassesOSStacksBytes,
+ goMemoryClassesMetadataMSpanInuseBytes,
+ goMemoryClassesMetadataMSPanFreeBytes,
+ goMemoryClassesMetadataMCacheInuseBytes,
+ goMemoryClassesMetadataMCacheFreeBytes,
+ goMemoryClassesProfilingBucketsBytes,
+ goMemoryClassesMetadataOtherBytes,
+ goMemoryClassesOtherBytes,
+}
+
+func bestEffortLookupRM(lookup []string) []metrics.Description {
+ ret := make([]metrics.Description, 0, len(lookup))
+ for _, rm := range metrics.All() {
+ for _, m := range lookup {
+ if m == rm.Name {
+ ret = append(ret, rm)
+ }
+ }
+ }
+ return ret
+}
+
type goCollector struct {
base baseGoCollector
@@ -36,70 +98,124 @@ type goCollector struct {
// snapshot is always produced by Collect.
mu sync.Mutex
- // rm... fields all pertain to the runtime/metrics package.
- rmSampleBuf []metrics.Sample
- rmSampleMap map[string]*metrics.Sample
- rmMetrics []collectorMetric
+ // Contains all samples that has to retrieved from runtime/metrics (not all of them will be exposed).
+ sampleBuf []metrics.Sample
+ // sampleMap allows lookup for MemStats metrics and runtime/metrics histograms for exact sums.
+ sampleMap map[string]*metrics.Sample
+
+ // rmExposedMetrics represents all runtime/metrics package metrics
+ // that were configured to be exposed.
+ rmExposedMetrics []collectorMetric
+ rmExactSumMapForHist map[string]string
// With Go 1.17, the runtime/metrics package was introduced.
// From that point on, metric names produced by the runtime/metrics
// package could be generated from runtime/metrics names. However,
// these differ from the old names for the same values.
//
- // This field exist to export the same values under the old names
+ // This field exists to export the same values under the old names
// as well.
- msMetrics memStatsMetrics
+ msMetrics memStatsMetrics
+ msMetricsEnabled bool
+}
+
+type rmMetricDesc struct {
+ metrics.Description
+}
+
+func matchRuntimeMetricsRules(rules []internal.GoCollectorRule) []rmMetricDesc {
+ var descs []rmMetricDesc
+ for _, d := range metrics.All() {
+ var (
+ deny = true
+ desc rmMetricDesc
+ )
+
+ for _, r := range rules {
+ if !r.Matcher.MatchString(d.Name) {
+ continue
+ }
+ deny = r.Deny
+ }
+ if deny {
+ continue
+ }
+
+ desc.Description = d
+ descs = append(descs, desc)
+ }
+ return descs
+}
+
+func defaultGoCollectorOptions() internal.GoCollectorOptions {
+ return internal.GoCollectorOptions{
+ RuntimeMetricSumForHist: map[string]string{
+ "/gc/heap/allocs-by-size:bytes": goGCHeapAllocsBytes,
+ "/gc/heap/frees-by-size:bytes": goGCHeapFreesBytes,
+ },
+ RuntimeMetricRules: []internal.GoCollectorRule{
+ //{Matcher: regexp.MustCompile("")},
+ },
+ }
}
// NewGoCollector is the obsolete version of collectors.NewGoCollector.
// See there for documentation.
//
// Deprecated: Use collectors.NewGoCollector instead.
-func NewGoCollector() Collector {
- descriptions := metrics.All()
+func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
+ opt := defaultGoCollectorOptions()
+ for _, o := range opts {
+ o(&opt)
+ }
+
+ exposedDescriptions := matchRuntimeMetricsRules(opt.RuntimeMetricRules)
// Collect all histogram samples so that we can get their buckets.
// The API guarantees that the buckets are always fixed for the lifetime
// of the process.
var histograms []metrics.Sample
- for _, d := range descriptions {
+ for _, d := range exposedDescriptions {
if d.Kind == metrics.KindFloat64Histogram {
histograms = append(histograms, metrics.Sample{Name: d.Name})
}
}
- metrics.Read(histograms)
+
+ if len(histograms) > 0 {
+ metrics.Read(histograms)
+ }
+
bucketsMap := make(map[string][]float64)
for i := range histograms {
bucketsMap[histograms[i].Name] = histograms[i].Value.Float64Histogram().Buckets
}
- // Generate a Desc and ValueType for each runtime/metrics metric.
- metricSet := make([]collectorMetric, 0, len(descriptions))
- sampleBuf := make([]metrics.Sample, 0, len(descriptions))
- sampleMap := make(map[string]*metrics.Sample, len(descriptions))
- for i := range descriptions {
- d := &descriptions[i]
- namespace, subsystem, name, ok := internal.RuntimeMetricsToProm(d)
+ // Generate a collector for each exposed runtime/metrics metric.
+ metricSet := make([]collectorMetric, 0, len(exposedDescriptions))
+ // SampleBuf is used for reading from runtime/metrics.
+ // We are assuming the largest case to have stable pointers for sampleMap purposes.
+ sampleBuf := make([]metrics.Sample, 0, len(exposedDescriptions)+len(opt.RuntimeMetricSumForHist)+len(rmNamesForMemStatsMetrics))
+ sampleMap := make(map[string]*metrics.Sample, len(exposedDescriptions))
+ for _, d := range exposedDescriptions {
+ namespace, subsystem, name, ok := internal.RuntimeMetricsToProm(&d.Description)
if !ok {
// Just ignore this metric; we can't do anything with it here.
// If a user decides to use the latest version of Go, we don't want
- // to fail here. This condition is tested elsewhere.
+ // to fail here. This condition is tested in TestExpectedRuntimeMetrics.
continue
}
- // Set up sample buffer for reading, and a map
- // for quick lookup of sample values.
sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name})
sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1]
var m collectorMetric
if d.Kind == metrics.KindFloat64Histogram {
- _, hasSum := rmExactSumMap[d.Name]
+ _, hasSum := opt.RuntimeMetricSumForHist[d.Name]
unit := d.Name[strings.IndexRune(d.Name, ':')+1:]
m = newBatchHistogram(
NewDesc(
BuildFQName(namespace, subsystem, name),
- d.Description,
+ d.Description.Description,
nil,
nil,
),
@@ -111,24 +227,61 @@ func NewGoCollector() Collector {
Namespace: namespace,
Subsystem: subsystem,
Name: name,
- Help: d.Description,
- })
+ Help: d.Description.Description,
+ },
+ )
} else {
m = NewGauge(GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: name,
- Help: d.Description,
+ Help: d.Description.Description,
})
}
metricSet = append(metricSet, m)
}
+
+ // Add exact sum metrics to sampleBuf if not added before.
+ for _, h := range histograms {
+ sumMetric, ok := opt.RuntimeMetricSumForHist[h.Name]
+ if !ok {
+ continue
+ }
+
+ if _, ok := sampleMap[sumMetric]; ok {
+ continue
+ }
+ sampleBuf = append(sampleBuf, metrics.Sample{Name: sumMetric})
+ sampleMap[sumMetric] = &sampleBuf[len(sampleBuf)-1]
+ }
+
+ var (
+ msMetrics memStatsMetrics
+ msDescriptions []metrics.Description
+ )
+
+ if !opt.DisableMemStatsLikeMetrics {
+ msMetrics = goRuntimeMemStats()
+ msDescriptions = bestEffortLookupRM(rmNamesForMemStatsMetrics)
+
+ // Check if metric was not exposed before and if not, add to sampleBuf.
+ for _, mdDesc := range msDescriptions {
+ if _, ok := sampleMap[mdDesc.Name]; ok {
+ continue
+ }
+ sampleBuf = append(sampleBuf, metrics.Sample{Name: mdDesc.Name})
+ sampleMap[mdDesc.Name] = &sampleBuf[len(sampleBuf)-1]
+ }
+ }
+
return &goCollector{
- base: newBaseGoCollector(),
- rmSampleBuf: sampleBuf,
- rmSampleMap: sampleMap,
- rmMetrics: metricSet,
- msMetrics: goRuntimeMemStats(),
+ base: newBaseGoCollector(),
+ sampleBuf: sampleBuf,
+ sampleMap: sampleMap,
+ rmExposedMetrics: metricSet,
+ rmExactSumMapForHist: opt.RuntimeMetricSumForHist,
+ msMetrics: msMetrics,
+ msMetricsEnabled: !opt.DisableMemStatsLikeMetrics,
}
}
@@ -138,7 +291,7 @@ func (c *goCollector) Describe(ch chan<- *Desc) {
for _, i := range c.msMetrics {
ch <- i.desc
}
- for _, m := range c.rmMetrics {
+ for _, m := range c.rmExposedMetrics {
ch <- m.Desc()
}
}
@@ -148,8 +301,12 @@ func (c *goCollector) Collect(ch chan<- Metric) {
// Collect base non-memory metrics.
c.base.Collect(ch)
+ if len(c.sampleBuf) == 0 {
+ return
+ }
+
// Collect must be thread-safe, so prevent concurrent use of
- // rmSampleBuf. Just read into rmSampleBuf but write all the data
+ // sampleBuf elements. Just read into sampleBuf but write all the data
// we get into our Metrics or MemStats.
//
// This lock also ensures that the Metrics we send out are all from
@@ -164,14 +321,17 @@ func (c *goCollector) Collect(ch chan<- Metric) {
defer c.mu.Unlock()
// Populate runtime/metrics sample buffer.
- metrics.Read(c.rmSampleBuf)
+ metrics.Read(c.sampleBuf)
+
+ // Collect all our runtime/metrics user chose to expose from sampleBuf (if any).
+ for i, metric := range c.rmExposedMetrics {
+ // We created samples for exposed metrics first in order, so indexes match.
+ sample := c.sampleBuf[i]
- // Update all our metrics from rmSampleBuf.
- for i, sample := range c.rmSampleBuf {
// N.B. switch on concrete type because it's significantly more efficient
// than checking for the Counter and Gauge interface implementations. In
// this case, we control all the types here.
- switch m := c.rmMetrics[i].(type) {
+ switch m := metric.(type) {
case *counter:
// Guard against decreases. This should never happen, but a failure
// to do so will result in a panic, which is a harsh consequence for
@@ -191,12 +351,15 @@ func (c *goCollector) Collect(ch chan<- Metric) {
panic("unexpected metric type")
}
}
- // ms is a dummy MemStats that we populate ourselves so that we can
- // populate the old metrics from it.
- var ms runtime.MemStats
- memStatsFromRM(&ms, c.rmSampleMap)
- for _, i := range c.msMetrics {
- ch <- MustNewConstMetric(i.desc, i.valType, i.eval(&ms))
+
+ if c.msMetricsEnabled {
+ // ms is a dummy MemStats that we populate ourselves so that we can
+ // populate the old metrics from it if goMemStatsCollection is enabled.
+ var ms runtime.MemStats
+ memStatsFromRM(&ms, c.sampleMap)
+ for _, i := range c.msMetrics {
+ ch <- MustNewConstMetric(i.desc, i.valType, i.eval(&ms))
+ }
}
}
@@ -224,11 +387,6 @@ func unwrapScalarRMValue(v metrics.Value) float64 {
}
}
-var rmExactSumMap = map[string]string{
- "/gc/heap/allocs-by-size:bytes": "/gc/heap/allocs:bytes",
- "/gc/heap/frees-by-size:bytes": "/gc/heap/frees:bytes",
-}
-
// exactSumFor takes a runtime/metrics metric name (that is assumed to
// be of kind KindFloat64Histogram) and returns its exact sum and whether
// its exact sum exists.
@@ -236,11 +394,11 @@ var rmExactSumMap = map[string]string{
// The runtime/metrics API for histograms doesn't currently expose exact
// sums, but some of the other metrics are in fact exact sums of histograms.
func (c *goCollector) exactSumFor(rmName string) float64 {
- sumName, ok := rmExactSumMap[rmName]
+ sumName, ok := c.rmExactSumMapForHist[rmName]
if !ok {
return 0
}
- s, ok := c.rmSampleMap[sumName]
+ s, ok := c.sampleMap[sumName]
if !ok {
return 0
}
@@ -261,35 +419,30 @@ func memStatsFromRM(ms *runtime.MemStats, rm map[string]*metrics.Sample) {
// while having Mallocs - Frees still represent a live object count.
// Unfortunately, MemStats doesn't actually export a large allocation count,
// so it's impossible to pull this number out directly.
- tinyAllocs := lookupOrZero("/gc/heap/tiny/allocs:objects")
- ms.Mallocs = lookupOrZero("/gc/heap/allocs:objects") + tinyAllocs
- ms.Frees = lookupOrZero("/gc/heap/frees:objects") + tinyAllocs
+ tinyAllocs := lookupOrZero(goGCHeapTinyAllocsObjects)
+ ms.Mallocs = lookupOrZero(goGCHeapAllocsObjects) + tinyAllocs
+ ms.Frees = lookupOrZero(goGCHeapFreesObjects) + tinyAllocs
- ms.TotalAlloc = lookupOrZero("/gc/heap/allocs:bytes")
- ms.Sys = lookupOrZero("/memory/classes/total:bytes")
+ ms.TotalAlloc = lookupOrZero(goGCHeapAllocsBytes)
+ ms.Sys = lookupOrZero(goMemoryClassesTotalBytes)
ms.Lookups = 0 // Already always zero.
- ms.HeapAlloc = lookupOrZero("/memory/classes/heap/objects:bytes")
+ ms.HeapAlloc = lookupOrZero(goMemoryClassesHeapObjectsBytes)
ms.Alloc = ms.HeapAlloc
- ms.HeapInuse = ms.HeapAlloc + lookupOrZero("/memory/classes/heap/unused:bytes")
- ms.HeapReleased = lookupOrZero("/memory/classes/heap/released:bytes")
- ms.HeapIdle = ms.HeapReleased + lookupOrZero("/memory/classes/heap/free:bytes")
+ ms.HeapInuse = ms.HeapAlloc + lookupOrZero(goMemoryClassesHeapUnusedBytes)
+ ms.HeapReleased = lookupOrZero(goMemoryClassesHeapReleasedBytes)
+ ms.HeapIdle = ms.HeapReleased + lookupOrZero(goMemoryClassesHeapFreeBytes)
ms.HeapSys = ms.HeapInuse + ms.HeapIdle
- ms.HeapObjects = lookupOrZero("/gc/heap/objects:objects")
- ms.StackInuse = lookupOrZero("/memory/classes/heap/stacks:bytes")
- ms.StackSys = ms.StackInuse + lookupOrZero("/memory/classes/os-stacks:bytes")
- ms.MSpanInuse = lookupOrZero("/memory/classes/metadata/mspan/inuse:bytes")
- ms.MSpanSys = ms.MSpanInuse + lookupOrZero("/memory/classes/metadata/mspan/free:bytes")
- ms.MCacheInuse = lookupOrZero("/memory/classes/metadata/mcache/inuse:bytes")
- ms.MCacheSys = ms.MCacheInuse + lookupOrZero("/memory/classes/metadata/mcache/free:bytes")
- ms.BuckHashSys = lookupOrZero("/memory/classes/profiling/buckets:bytes")
- ms.GCSys = lookupOrZero("/memory/classes/metadata/other:bytes")
- ms.OtherSys = lookupOrZero("/memory/classes/other:bytes")
- ms.NextGC = lookupOrZero("/gc/heap/goal:bytes")
-
- // N.B. LastGC is omitted because runtime.GCStats already has this.
- // See https://github.com/prometheus/client_golang/issues/842#issuecomment-861812034
- // for more details.
- ms.LastGC = 0
+ ms.HeapObjects = lookupOrZero(goGCHeapObjects)
+ ms.StackInuse = lookupOrZero(goMemoryClassesHeapStacksBytes)
+ ms.StackSys = ms.StackInuse + lookupOrZero(goMemoryClassesOSStacksBytes)
+ ms.MSpanInuse = lookupOrZero(goMemoryClassesMetadataMSpanInuseBytes)
+ ms.MSpanSys = ms.MSpanInuse + lookupOrZero(goMemoryClassesMetadataMSPanFreeBytes)
+ ms.MCacheInuse = lookupOrZero(goMemoryClassesMetadataMCacheInuseBytes)
+ ms.MCacheSys = ms.MCacheInuse + lookupOrZero(goMemoryClassesMetadataMCacheFreeBytes)
+ ms.BuckHashSys = lookupOrZero(goMemoryClassesProfilingBucketsBytes)
+ ms.GCSys = lookupOrZero(goMemoryClassesMetadataOtherBytes)
+ ms.OtherSys = lookupOrZero(goMemoryClassesOtherBytes)
+ ms.NextGC = lookupOrZero(goGCHeapGoalBytes)
// N.B. GCCPUFraction is intentionally omitted. This metric is not useful,
// and often misleading due to the fact that it's an average over the lifetime
@@ -324,6 +477,11 @@ type batchHistogram struct {
// buckets must always be from the runtime/metrics package, following
// the same conventions.
func newBatchHistogram(desc *Desc, buckets []float64, hasSum bool) *batchHistogram {
+ // We need to remove -Inf values. runtime/metrics keeps them around.
+ // But -Inf bucket should not be allowed for prometheus histograms.
+ if buckets[0] == math.Inf(-1) {
+ buckets = buckets[1:]
+ }
h := &batchHistogram{
desc: desc,
buckets: buckets,
@@ -382,8 +540,10 @@ func (h *batchHistogram) Write(out *dto.Metric) error {
for i, count := range h.counts {
totalCount += count
if !h.hasSum {
- // N.B. This computed sum is an underestimate.
- sum += h.buckets[i] * float64(count)
+ if count != 0 {
+ // N.B. This computed sum is an underestimate.
+ sum += h.buckets[i] * float64(count)
+ }
}
// Skip the +Inf bucket, but only for the bucket list.
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go
index 893802fd6..4c873a01c 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go
@@ -28,19 +28,216 @@ import (
dto "github.com/prometheus/client_model/go"
)
+// 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
+// here for convenience of using the schema directly as the index.)
+//
+// TODO(beorn7): Currently, we do a binary search into these slices. There are
+// ways to turn it into a small number of simple array lookups. It probably only
+// matters for schema 5 and beyond, but should be investigated. See this comment
+// as a starting point:
+// https://github.com/open-telemetry/opentelemetry-specification/issues/1776#issuecomment-870164310
+var nativeHistogramBounds = [][]float64{
+ // Schema "0":
+ {0.5},
+ // Schema 1:
+ {0.5, 0.7071067811865475},
+ // Schema 2:
+ {0.5, 0.5946035575013605, 0.7071067811865475, 0.8408964152537144},
+ // Schema 3:
+ {
+ 0.5, 0.5452538663326288, 0.5946035575013605, 0.6484197773255048,
+ 0.7071067811865475, 0.7711054127039704, 0.8408964152537144, 0.9170040432046711,
+ },
+ // Schema 4:
+ {
+ 0.5, 0.5221368912137069, 0.5452538663326288, 0.5693943173783458,
+ 0.5946035575013605, 0.620928906036742, 0.6484197773255048, 0.6771277734684463,
+ 0.7071067811865475, 0.7384130729697496, 0.7711054127039704, 0.805245165974627,
+ 0.8408964152537144, 0.8781260801866495, 0.9170040432046711, 0.9576032806985735,
+ },
+ // Schema 5:
+ {
+ 0.5, 0.5109485743270583, 0.5221368912137069, 0.5335702003384117,
+ 0.5452538663326288, 0.5571933712979462, 0.5693943173783458, 0.5818624293887887,
+ 0.5946035575013605, 0.6076236799902344, 0.620928906036742, 0.6345254785958666,
+ 0.6484197773255048, 0.6626183215798706, 0.6771277734684463, 0.6919549409819159,
+ 0.7071067811865475, 0.7225904034885232, 0.7384130729697496, 0.7545822137967112,
+ 0.7711054127039704, 0.7879904225539431, 0.805245165974627, 0.8228777390769823,
+ 0.8408964152537144, 0.8593096490612387, 0.8781260801866495, 0.8973545375015533,
+ 0.9170040432046711, 0.9370838170551498, 0.9576032806985735, 0.9785720620876999,
+ },
+ // Schema 6:
+ {
+ 0.5, 0.5054446430258502, 0.5109485743270583, 0.5165124395106142,
+ 0.5221368912137069, 0.5278225891802786, 0.5335702003384117, 0.5393803988785598,
+ 0.5452538663326288, 0.5511912916539204, 0.5571933712979462, 0.5632608093041209,
+ 0.5693943173783458, 0.5755946149764913, 0.5818624293887887, 0.5881984958251406,
+ 0.5946035575013605, 0.6010783657263515, 0.6076236799902344, 0.6142402680534349,
+ 0.620928906036742, 0.6276903785123455, 0.6345254785958666, 0.6414350080393891,
+ 0.6484197773255048, 0.6554806057623822, 0.6626183215798706, 0.6698337620266515,
+ 0.6771277734684463, 0.6845012114872953, 0.6919549409819159, 0.6994898362691555,
+ 0.7071067811865475, 0.7148066691959849, 0.7225904034885232, 0.7304588970903234,
+ 0.7384130729697496, 0.7464538641456323, 0.7545822137967112, 0.762799075372269,
+ 0.7711054127039704, 0.7795022001189185, 0.7879904225539431, 0.7965710756711334,
+ 0.805245165974627, 0.8140137109286738, 0.8228777390769823, 0.8318382901633681,
+ 0.8408964152537144, 0.8500531768592616, 0.8593096490612387, 0.8686669176368529,
+ 0.8781260801866495, 0.8876882462632604, 0.8973545375015533, 0.9071260877501991,
+ 0.9170040432046711, 0.9269895625416926, 0.9370838170551498, 0.9472879907934827,
+ 0.9576032806985735, 0.9680308967461471, 0.9785720620876999, 0.9892280131939752,
+ },
+ // Schema 7:
+ {
+ 0.5, 0.5027149505564014, 0.5054446430258502, 0.5081891574554764,
+ 0.5109485743270583, 0.5137229745593818, 0.5165124395106142, 0.5193170509806894,
+ 0.5221368912137069, 0.5249720429003435, 0.5278225891802786, 0.5306886136446309,
+ 0.5335702003384117, 0.5364674337629877, 0.5393803988785598, 0.5423091811066545,
+ 0.5452538663326288, 0.5482145409081883, 0.5511912916539204, 0.5541842058618393,
+ 0.5571933712979462, 0.5602188762048033, 0.5632608093041209, 0.5663192597993595,
+ 0.5693943173783458, 0.572486072215902, 0.5755946149764913, 0.5787200368168754,
+ 0.5818624293887887, 0.585021884841625, 0.5881984958251406, 0.5913923554921704,
+ 0.5946035575013605, 0.5978321960199137, 0.6010783657263515, 0.6043421618132907,
+ 0.6076236799902344, 0.6109230164863786, 0.6142402680534349, 0.6175755319684665,
+ 0.620928906036742, 0.6243004885946023, 0.6276903785123455, 0.6310986751971253,
+ 0.6345254785958666, 0.637970889198196, 0.6414350080393891, 0.6449179367033329,
+ 0.6484197773255048, 0.6519406325959679, 0.6554806057623822, 0.659039800633032,
+ 0.6626183215798706, 0.6662162735415805, 0.6698337620266515, 0.6734708931164728,
+ 0.6771277734684463, 0.6808045103191123, 0.6845012114872953, 0.688217985377265,
+ 0.6919549409819159, 0.6957121878859629, 0.6994898362691555, 0.7032879969095076,
+ 0.7071067811865475, 0.7109463010845827, 0.7148066691959849, 0.718687998724491,
+ 0.7225904034885232, 0.7265139979245261, 0.7304588970903234, 0.7344252166684908,
+ 0.7384130729697496, 0.7424225829363761, 0.7464538641456323, 0.7505070348132126,
+ 0.7545822137967112, 0.7586795205991071, 0.762799075372269, 0.7669409989204777,
+ 0.7711054127039704, 0.7752924388424999, 0.7795022001189185, 0.7837348199827764,
+ 0.7879904225539431, 0.7922691326262467, 0.7965710756711334, 0.8008963778413465,
+ 0.805245165974627, 0.8096175675974316, 0.8140137109286738, 0.8184337248834821,
+ 0.8228777390769823, 0.8273458838280969, 0.8318382901633681, 0.8363550898207981,
+ 0.8408964152537144, 0.8454623996346523, 0.8500531768592616, 0.8546688815502312,
+ 0.8593096490612387, 0.8639756154809185, 0.8686669176368529, 0.8733836930995842,
+ 0.8781260801866495, 0.8828942179666361, 0.8876882462632604, 0.8925083056594671,
+ 0.8973545375015533, 0.9022270839033115, 0.9071260877501991, 0.9120516927035263,
+ 0.9170040432046711, 0.9219832844793128, 0.9269895625416926, 0.9320230241988943,
+ 0.9370838170551498, 0.9421720895161669, 0.9472879907934827, 0.9524316709088368,
+ 0.9576032806985735, 0.9628029718180622, 0.9680308967461471, 0.9732872087896164,
+ 0.9785720620876999, 0.9838856116165875, 0.9892280131939752, 0.9945994234836328,
+ },
+ // Schema 8:
+ {
+ 0.5, 0.5013556375251013, 0.5027149505564014, 0.5040779490592088,
+ 0.5054446430258502, 0.5068150424757447, 0.5081891574554764, 0.509566998038869,
+ 0.5109485743270583, 0.5123338964485679, 0.5137229745593818, 0.5151158188430205,
+ 0.5165124395106142, 0.5179128468009786, 0.5193170509806894, 0.520725062344158,
+ 0.5221368912137069, 0.5235525479396449, 0.5249720429003435, 0.526395386502313,
+ 0.5278225891802786, 0.5292536613972564, 0.5306886136446309, 0.5321274564422321,
+ 0.5335702003384117, 0.5350168559101208, 0.5364674337629877, 0.5379219445313954,
+ 0.5393803988785598, 0.5408428074966075, 0.5423091811066545, 0.5437795304588847,
+ 0.5452538663326288, 0.5467321995364429, 0.5482145409081883, 0.549700901315111,
+ 0.5511912916539204, 0.5526857228508706, 0.5541842058618393, 0.5556867516724088,
+ 0.5571933712979462, 0.5587040757836845, 0.5602188762048033, 0.5617377836665098,
+ 0.5632608093041209, 0.564787964283144, 0.5663192597993595, 0.5678547070789026,
+ 0.5693943173783458, 0.5709381019847808, 0.572486072215902, 0.5740382394200894,
+ 0.5755946149764913, 0.5771552102951081, 0.5787200368168754, 0.5802891060137493,
+ 0.5818624293887887, 0.5834400184762408, 0.585021884841625, 0.5866080400818185,
+ 0.5881984958251406, 0.5897932637314379, 0.5913923554921704, 0.5929957828304968,
+ 0.5946035575013605, 0.5962156912915756, 0.5978321960199137, 0.5994530835371903,
+ 0.6010783657263515, 0.6027080545025619, 0.6043421618132907, 0.6059806996384005,
+ 0.6076236799902344, 0.6092711149137041, 0.6109230164863786, 0.6125793968185725,
+ 0.6142402680534349, 0.6159056423670379, 0.6175755319684665, 0.6192499490999082,
+ 0.620928906036742, 0.622612415087629, 0.6243004885946023, 0.6259931389331581,
+ 0.6276903785123455, 0.6293922197748583, 0.6310986751971253, 0.6328097572894031,
+ 0.6345254785958666, 0.6362458516947014, 0.637970889198196, 0.6397006037528346,
+ 0.6414350080393891, 0.6431741147730128, 0.6449179367033329, 0.6466664866145447,
+ 0.6484197773255048, 0.6501778216898253, 0.6519406325959679, 0.6537082229673385,
+ 0.6554806057623822, 0.6572577939746774, 0.659039800633032, 0.6608266388015788,
+ 0.6626183215798706, 0.6644148621029772, 0.6662162735415805, 0.6680225691020727,
+ 0.6698337620266515, 0.6716498655934177, 0.6734708931164728, 0.6752968579460171,
+ 0.6771277734684463, 0.6789636531064505, 0.6808045103191123, 0.6826503586020058,
+ 0.6845012114872953, 0.6863570825438342, 0.688217985377265, 0.690083933630119,
+ 0.6919549409819159, 0.6938310211492645, 0.6957121878859629, 0.6975984549830999,
+ 0.6994898362691555, 0.7013863456101023, 0.7032879969095076, 0.7051948041086352,
+ 0.7071067811865475, 0.7090239421602076, 0.7109463010845827, 0.7128738720527471,
+ 0.7148066691959849, 0.7167447066838943, 0.718687998724491, 0.7206365595643126,
+ 0.7225904034885232, 0.7245495448210174, 0.7265139979245261, 0.7284837772007218,
+ 0.7304588970903234, 0.7324393720732029, 0.7344252166684908, 0.7364164454346837,
+ 0.7384130729697496, 0.7404151139112358, 0.7424225829363761, 0.7444354947621984,
+ 0.7464538641456323, 0.7484777058836176, 0.7505070348132126, 0.7525418658117031,
+ 0.7545822137967112, 0.7566280937263048, 0.7586795205991071, 0.7607365094544071,
+ 0.762799075372269, 0.7648672334736434, 0.7669409989204777, 0.7690203869158282,
+ 0.7711054127039704, 0.7731960915705107, 0.7752924388424999, 0.7773944698885442,
+ 0.7795022001189185, 0.7816156449856788, 0.7837348199827764, 0.7858597406461707,
+ 0.7879904225539431, 0.7901268813264122, 0.7922691326262467, 0.7944171921585818,
+ 0.7965710756711334, 0.7987307989543135, 0.8008963778413465, 0.8030678282083853,
+ 0.805245165974627, 0.8074284071024302, 0.8096175675974316, 0.8118126635086642,
+ 0.8140137109286738, 0.8162207259936375, 0.8184337248834821, 0.820652723822003,
+ 0.8228777390769823, 0.8251087869603088, 0.8273458838280969, 0.8295890460808079,
+ 0.8318382901633681, 0.8340936325652911, 0.8363550898207981, 0.8386226785089391,
+ 0.8408964152537144, 0.8431763167241966, 0.8454623996346523, 0.8477546807446661,
+ 0.8500531768592616, 0.8523579048290255, 0.8546688815502312, 0.8569861239649629,
+ 0.8593096490612387, 0.8616394738731368, 0.8639756154809185, 0.8663180910111553,
+ 0.8686669176368529, 0.871022112577578, 0.8733836930995842, 0.8757516765159389,
+ 0.8781260801866495, 0.8805069215187917, 0.8828942179666361, 0.8852879870317771,
+ 0.8876882462632604, 0.890095013257712, 0.8925083056594671, 0.8949281411607002,
+ 0.8973545375015533, 0.8997875124702672, 0.9022270839033115, 0.9046732696855155,
+ 0.9071260877501991, 0.909585556079304, 0.9120516927035263, 0.9145245157024483,
+ 0.9170040432046711, 0.9194902933879467, 0.9219832844793128, 0.9244830347552253,
+ 0.9269895625416926, 0.92950288621441, 0.9320230241988943, 0.9345499949706191,
+ 0.9370838170551498, 0.93962450902828, 0.9421720895161669, 0.9447265771954693,
+ 0.9472879907934827, 0.9498563490882775, 0.9524316709088368, 0.9550139751351947,
+ 0.9576032806985735, 0.9601996065815236, 0.9628029718180622, 0.9654133954938133,
+ 0.9680308967461471, 0.9706554947643201, 0.9732872087896164, 0.9759260581154889,
+ 0.9785720620876999, 0.9812252401044634, 0.9838856116165875, 0.9865531961276168,
+ 0.9892280131939752, 0.9919100824251095, 0.9945994234836328, 0.9972960560854698,
+ },
+}
+
+// The nativeHistogramBounds above can be generated with the code below.
+//
+// TODO(beorn7): It's tempting to actually use `go generate` to generate the
+// code above. However, this could lead to slightly different numbers on
+// different architectures. We still need to come to terms if we are fine with
+// that, or if we might prefer to specify precise numbers in the standard.
+//
+// var nativeHistogramBounds [][]float64 = make([][]float64, 9)
+//
+// func init() {
+// // Populate nativeHistogramBounds.
+// numBuckets := 1
+// for i := range nativeHistogramBounds {
+// bounds := []float64{0.5}
+// factor := math.Exp2(math.Exp2(float64(-i)))
+// for j := 0; j < numBuckets-1; j++ {
+// var bound float64
+// if (j+1)%2 == 0 {
+// // Use previously calculated value for increased precision.
+// bound = nativeHistogramBounds[i-1][j/2+1]
+// } else {
+// bound = bounds[j] * factor
+// }
+// bounds = append(bounds, bound)
+// }
+// numBuckets *= 2
+// nativeHistogramBounds[i] = bounds
+// }
+// }
+
// 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.
+// configurable static buckets (or in dynamic sparse buckets as part of the
+// experimental Native Histograms, see below for more details). 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.
+// the histogram_quantile PromQL function.
+//
+// Note that Histograms, in contrast to Summaries, can be aggregated in PromQL
+// (see the documentation for detailed procedures). However, Histograms require
+// the user to pre-define suitable buckets, and they are in general less
+// accurate. (Both problems are addressed by the experimental Native
+// Histograms. To use them, configure a NativeHistogramBucketFactor in the
+// HistogramOpts. They also require a Prometheus server v2.40+ with the
+// corresponding feature flag enabled.)
//
-// Note that Histograms, in contrast to Summaries, can be aggregated with the
-// Prometheus query language (see the documentation for detailed
-// procedures). However, Histograms require the user to pre-define suitable
-// 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.
+// 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 {
@@ -50,7 +247,8 @@ type Histogram interface {
// Observe adds a single observation to the histogram. Observations are
// usually positive or zero. Negative observations are accepted but
// prevent current versions of Prometheus from properly detecting
- // counter resets in the sum of observations. See
+ // counter resets in the sum of observations. (The experimental Native
+ // Histograms handle negative observations properly.) See
// https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations
// for details.
Observe(float64)
@@ -64,18 +262,28 @@ const bucketLabel = "le"
// 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.
-var (
- DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
+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,
- )
+// DefNativeHistogramZeroThreshold is the default value for
+// NativeHistogramZeroThreshold in the HistogramOpts.
+//
+// The value is 2^-128 (or 0.5*2^-127 in the actual IEEE 754 representation),
+// which is a bucket boundary at all possible resolutions.
+const DefNativeHistogramZeroThreshold = 2.938735877055719e-39
+
+// NativeHistogramZeroThresholdZero can be used as NativeHistogramZeroThreshold
+// in the HistogramOpts to create a zero bucket of width zero, i.e. a zero
+// bucket that only receives observations of precisely zero.
+const NativeHistogramZeroThresholdZero = -1
+
+var errBucketLabelNotAllowed = fmt.Errorf(
+ "%q is not allowed as label name in histograms", bucketLabel,
)
-// LinearBuckets creates 'count' buckets, each 'width' wide, where the lowest
-// 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.
+// LinearBuckets creates 'count' regular buckets, each 'width' wide, where the
+// lowest 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.
//
// The function panics if 'count' is zero or negative.
func LinearBuckets(start, width float64, count int) []float64 {
@@ -90,11 +298,11 @@ func LinearBuckets(start, width float64, count int) []float64 {
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'
-// 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.
+// ExponentialBuckets creates 'count' regular buckets, where the lowest bucket
+// has an upper bound of 'start' and each following bucket's upper bound is
+// 'factor' 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.
//
// The function panics if 'count' is 0 or negative, if 'start' is 0 or negative,
// or if 'factor' is less than or equal 1.
@@ -180,8 +388,85 @@ type HistogramOpts struct {
// 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
- // implicitly. The default value is DefBuckets.
+ // implicitly. If Buckets is left as nil or set to a slice of length
+ // zero, it is replaced by default buckets. The default buckets are
+ // 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
+ // use both reguler buckets and buckets for a native histogram, you have
+ // to define the regular buckets here explicitly.)
Buckets []float64
+
+ // If NativeHistogramBucketFactor is greater than one, so-called sparse
+ // buckets are used (in addition to the regular buckets, if defined
+ // above). A Histogram with sparse buckets will be ingested as a Native
+ // Histogram by a Prometheus server with that feature enabled (requires
+ // Prometheus v2.40+). Sparse buckets are exponential buckets covering
+ // the whole float64 range (with the exception of the “zero” bucket, see
+ // SparseBucketsZeroThreshold below). From any one bucket to the next,
+ // the width of the bucket grows by a constant
+ // factor. NativeHistogramBucketFactor provides an upper bound for this
+ // factor (exception see below). The smaller
+ // NativeHistogramBucketFactor, the more buckets will be used and thus
+ // the more costly the histogram will become. A generally good trade-off
+ // between cost and accuracy is a value of 1.1 (each bucket is at most
+ // 10% wider than the previous one), which will result in each power of
+ // two divided into 8 buckets (e.g. there will be 8 buckets between 1
+ // and 2, same as between 2 and 4, and 4 and 8, etc.).
+ //
+ // 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
+ // still smaller or equal to NativeHistogramBucketFactor. Note that the
+ // smallest possible factor is therefore approx. 1.00271 (i.e. 2^(2^-8)
+ // ). If NativeHistogramBucketFactor is greater than 1 but smaller than
+ // 2^(2^-8), then the actually used factor is still 2^(2^-8) even though
+ // it is larger than the provided NativeHistogramBucketFactor.
+ //
+ // NOTE: Native Histograms are still an experimental feature. Their
+ // behavior might still change without a major version
+ // bump. Subsequently, all NativeHistogram... options here might still
+ // change their behavior or name (or might completely disappear) without
+ // a major version bump.
+ NativeHistogramBucketFactor float64
+ // 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 is left at zero,
+ // DefSparseBucketsZeroThreshold 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
+ // NativeHistogramZeroThreshold to the NativeHistogramZeroThresholdZero
+ // constant (or any negative float value).
+ NativeHistogramZeroThreshold float64
+
+ // The remaining fields define a strategy to limit the number of
+ // populated sparse buckets. If NativeHistogramMaxBucketNumber is left
+ // at zero, the number of buckets is not limited. (Note that this might
+ // lead to unbounded memory consumption if the values observed by the
+ // Histogram are sufficiently wide-spread. In particular, this could be
+ // used as a DoS attack vector. Where the observed values depend on
+ // external inputs, it is highly recommended to set a
+ // NativeHistogramMaxBucketNumber.) Once the set
+ // 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).
+ NativeHistogramMaxBucketNumber uint32
+ NativeHistogramMinResetDuration time.Duration
+ NativeHistogramMaxZeroThreshold float64
}
// NewHistogram creates a new Histogram based on the provided HistogramOpts. It
@@ -218,16 +503,29 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
}
}
- 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,
+ desc: desc,
+ upperBounds: opts.Buckets,
+ labelPairs: MakeLabelPairs(desc, labelValues),
+ nativeHistogramMaxBuckets: opts.NativeHistogramMaxBucketNumber,
+ nativeHistogramMaxZeroThreshold: opts.NativeHistogramMaxZeroThreshold,
+ nativeHistogramMinResetDuration: opts.NativeHistogramMinResetDuration,
+ lastResetTime: time.Now(),
+ now: time.Now,
+ }
+ if len(h.upperBounds) == 0 && opts.NativeHistogramBucketFactor <= 1 {
+ h.upperBounds = DefBuckets
+ }
+ if opts.NativeHistogramBucketFactor <= 1 {
+ h.nativeHistogramSchema = math.MinInt32 // To mark that there are no sparse buckets.
+ } else {
+ switch {
+ case opts.NativeHistogramZeroThreshold > 0:
+ h.nativeHistogramZeroThreshold = opts.NativeHistogramZeroThreshold
+ case opts.NativeHistogramZeroThreshold == 0:
+ h.nativeHistogramZeroThreshold = DefNativeHistogramZeroThreshold
+ } // Leave h.nativeHistogramZeroThreshold at 0 otherwise.
+ h.nativeHistogramSchema = pickSchema(opts.NativeHistogramBucketFactor)
}
for i, upperBound := range h.upperBounds {
if i < len(h.upperBounds)-1 {
@@ -246,8 +544,16 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
}
// Finally we know the final length of h.upperBounds and can make buckets
// for both counts as well as exemplars:
- h.counts[0].buckets = make([]uint64, len(h.upperBounds))
- h.counts[1].buckets = make([]uint64, len(h.upperBounds))
+ 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.exemplars = make([]atomic.Value, len(h.upperBounds)+1)
h.init(h) // Init self-collection.
@@ -255,13 +561,98 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
}
type histogramCounts struct {
+ // Order in this struct matters for the alignment required by atomic
+ // operations, see http://golang.org/pkg/sync/atomic/#pkg-note-BUG
+
// 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
+ // observations.
sumBits uint64
count uint64
+
+ // nativeHistogramZeroBucket counts all (positive and negative)
+ // observations in the zero bucket (with an absolute value less or equal
+ // the current threshold, see next field.
+ nativeHistogramZeroBucket uint64
+ // nativeHistogramZeroThresholdBits is the bit pattern of the current
+ // threshold for the zero bucket. It's initially equal to
+ // nativeHistogramZeroThreshold but may change according to the bucket
+ // count limitation strategy.
+ nativeHistogramZeroThresholdBits uint64
+ // nativeHistogramSchema may change over time according to the bucket
+ // count limitation strategy and therefore has to be saved here.
+ nativeHistogramSchema int32
+ // Number of (positive and negative) sparse buckets.
+ nativeHistogramBucketsNumber uint32
+
+ // Regular buckets.
buckets []uint64
+
+ // The sparse buckets for native histograms are implemented with a
+ // sync.Map for now. A dedicated data structure will likely be more
+ // efficient. There are separate maps for negative and positive
+ // observations. The map's value is an *int64, counting observations in
+ // that bucket. (Note that we don't use uint64 as an int64 won't
+ // overflow in practice, and working with signed numbers from the
+ // beginning simplifies the handling of deltas.) The map's key is the
+ // index of the bucket according to the used
+ // nativeHistogramSchema. Index 0 is for an upper bound of 1.
+ nativeHistogramBucketsPositive, nativeHistogramBucketsNegative sync.Map
+}
+
+// observe manages the parts of observe that only affects
+// histogramCounts. doSparse is true if sparse buckets should be done,
+// too.
+func (hc *histogramCounts) observe(v float64, bucket int, doSparse bool) {
+ if bucket < len(hc.buckets) {
+ atomic.AddUint64(&hc.buckets[bucket], 1)
+ }
+ atomicAddFloat(&hc.sumBits, v)
+ if doSparse && !math.IsNaN(v) {
+ var (
+ key int
+ schema = atomic.LoadInt32(&hc.nativeHistogramSchema)
+ zeroThreshold = math.Float64frombits(atomic.LoadUint64(&hc.nativeHistogramZeroThresholdBits))
+ bucketCreated, isInf bool
+ )
+ if math.IsInf(v, 0) {
+ // Pretend v is MaxFloat64 but later increment key by one.
+ if math.IsInf(v, +1) {
+ v = math.MaxFloat64
+ } else {
+ v = -math.MaxFloat64
+ }
+ isInf = true
+ }
+ frac, exp := math.Frexp(math.Abs(v))
+ if schema > 0 {
+ bounds := nativeHistogramBounds[schema]
+ key = sort.SearchFloat64s(bounds, frac) + (exp-1)*len(bounds)
+ } else {
+ key = exp
+ if frac == 0.5 {
+ key--
+ }
+ div := 1 << -schema
+ key = (key + div - 1) / div
+ }
+ if isInf {
+ key++
+ }
+ switch {
+ case v > zeroThreshold:
+ bucketCreated = addToBucket(&hc.nativeHistogramBucketsPositive, key, 1)
+ case v < -zeroThreshold:
+ bucketCreated = addToBucket(&hc.nativeHistogramBucketsNegative, key, 1)
+ default:
+ atomic.AddUint64(&hc.nativeHistogramZeroBucket, 1)
+ }
+ if bucketCreated {
+ atomic.AddUint32(&hc.nativeHistogramBucketsNumber, 1)
+ }
+ }
+ // Increment count last as we take it as a signal that the observation
+ // is complete.
+ atomic.AddUint64(&hc.count, 1)
}
type histogram struct {
@@ -276,7 +667,7 @@ type histogram struct {
// perspective of the histogram) swap the hot–cold under the writeMtx
// lock. A cooldown is awaited (while locked) by comparing the number of
// observations with the initiation count. Once they match, then the
- // last observation on the now cool one has completed. All cool fields must
+ // last observation on the now cool one has completed. All cold fields must
// be merged into the new hot before releasing writeMtx.
//
// Fields with atomic access first! See alignment constraint:
@@ -284,8 +675,10 @@ type histogram struct {
countAndHotIdx uint64
selfCollector
- desc *Desc
- writeMtx sync.Mutex // Only used in the Write method.
+ desc *Desc
+
+ // Only used in the Write method and for sparse bucket management.
+ mtx sync.Mutex
// Two counts, one is "hot" for lock-free observations, the other is
// "cold" for writing out a dto.Metric. It has to be an array of
@@ -293,9 +686,15 @@ type histogram struct {
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
counts [2]*histogramCounts
- upperBounds []float64
- labelPairs []*dto.LabelPair
- exemplars []atomic.Value // One more than buckets (to include +Inf), each a *dto.Exemplar.
+ upperBounds []float64
+ labelPairs []*dto.LabelPair
+ exemplars []atomic.Value // One more than buckets (to include +Inf), each a *dto.Exemplar.
+ nativeHistogramSchema int32 // The initial schema. Set to math.MinInt32 if no sparse buckets are used.
+ nativeHistogramZeroThreshold float64 // The initial zero threshold.
+ nativeHistogramMaxZeroThreshold float64
+ nativeHistogramMaxBuckets uint32
+ nativeHistogramMinResetDuration time.Duration
+ lastResetTime time.Time // Protected by mtx.
now func() time.Time // To mock out time.Now() for testing.
}
@@ -319,8 +718,8 @@ func (h *histogram) Write(out *dto.Metric) error {
// the hot path, i.e. Observe is called much more often than Write. The
// complication of making Write lock-free isn't worth it, if possible at
// all.
- h.writeMtx.Lock()
- defer h.writeMtx.Unlock()
+ h.mtx.Lock()
+ defer h.mtx.Unlock()
// Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
// without touching the count bits. See the struct comments for a full
@@ -333,16 +732,16 @@ func (h *histogram) Write(out *dto.Metric) error {
hotCounts := h.counts[n>>63]
coldCounts := h.counts[(^n)>>63]
- // Await cooldown.
- for count != atomic.LoadUint64(&coldCounts.count) {
- runtime.Gosched() // Let observations get work done.
- }
+ waitForCooldown(count, coldCounts)
his := &dto.Histogram{
Bucket: make([]*dto.Bucket, len(h.upperBounds)),
SampleCount: proto.Uint64(count),
SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
}
+ out.Histogram = his
+ out.Label = h.labelPairs
+
var cumCount uint64
for i, upperBound := range h.upperBounds {
cumCount += atomic.LoadUint64(&coldCounts.buckets[i])
@@ -363,25 +762,21 @@ func (h *histogram) Write(out *dto.Metric) error {
}
his.Bucket = append(his.Bucket, b)
}
-
- out.Histogram = his
- out.Label = h.labelPairs
-
- // 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)
+ if h.nativeHistogramSchema > math.MinInt32 {
+ his.ZeroThreshold = proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.nativeHistogramZeroThresholdBits)))
+ his.Schema = proto.Int32(atomic.LoadInt32(&coldCounts.nativeHistogramSchema))
+ zeroBucket := atomic.LoadUint64(&coldCounts.nativeHistogramZeroBucket)
+
+ defer func() {
+ coldCounts.nativeHistogramBucketsPositive.Range(addAndReset(&hotCounts.nativeHistogramBucketsPositive, &hotCounts.nativeHistogramBucketsNumber))
+ coldCounts.nativeHistogramBucketsNegative.Range(addAndReset(&hotCounts.nativeHistogramBucketsNegative, &hotCounts.nativeHistogramBucketsNumber))
+ }()
+
+ his.ZeroCount = proto.Uint64(zeroBucket)
+ his.NegativeSpan, his.NegativeDelta = makeBuckets(&coldCounts.nativeHistogramBucketsNegative)
+ his.PositiveSpan, his.PositiveDelta = makeBuckets(&coldCounts.nativeHistogramBucketsPositive)
}
+ addAndResetCounts(hotCounts, coldCounts)
return nil
}
@@ -402,25 +797,216 @@ func (h *histogram) findBucket(v float64) int {
// observe is the implementation for Observe without the findBucket part.
func (h *histogram) observe(v float64, bucket int) {
+ // Do not add to sparse buckets for NaN observations.
+ doSparse := h.nativeHistogramSchema > math.MinInt32 && !math.IsNaN(v)
// We increment h.countAndHotIdx so that the counter in the lower
// 63 bits gets incremented. 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, 1)
hotCounts := h.counts[n>>63]
+ hotCounts.observe(v, bucket, doSparse)
+ if doSparse {
+ h.limitBuckets(hotCounts, v, bucket)
+ }
+}
- if bucket < len(h.upperBounds) {
- atomic.AddUint64(&hotCounts.buckets[bucket], 1)
+// limitSparsebuckets applies a strategy to limit the number of populated sparse
+// 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
+// the number sufficiently, or if the provided counts aren't fully updated yet
+// by a concurrently happening Write call).
+func (h *histogram) limitBuckets(counts *histogramCounts, value float64, bucket int) {
+ if h.nativeHistogramMaxBuckets == 0 {
+ return // No limit configured.
}
- for {
- oldBits := atomic.LoadUint64(&hotCounts.sumBits)
- newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
- if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
- break
+ if h.nativeHistogramMaxBuckets >= atomic.LoadUint32(&counts.nativeHistogramBucketsNumber) {
+ return // Bucket limit not exceeded yet.
+ }
+
+ h.mtx.Lock()
+ defer h.mtx.Unlock()
+
+ // The hot counts might have been swapped just before we acquired the
+ // lock. Re-fetch the hot counts first...
+ n := atomic.LoadUint64(&h.countAndHotIdx)
+ hotIdx := n >> 63
+ coldIdx := (^n) >> 63
+ hotCounts := h.counts[hotIdx]
+ coldCounts := h.counts[coldIdx]
+ // ...and then check again if we really have to reduce the bucket count.
+ if h.nativeHistogramMaxBuckets >= atomic.LoadUint32(&hotCounts.nativeHistogramBucketsNumber) {
+ return // Bucket limit not exceeded after all.
+ }
+ // Try the various strategies in order.
+ if h.maybeReset(hotCounts, coldCounts, coldIdx, value, bucket) {
+ return
+ }
+ if h.maybeWidenZeroBucket(hotCounts, coldCounts) {
+ return
+ }
+ h.doubleBucketWidth(hotCounts, coldCounts)
+}
+
+// maybeReset resests the whole histogram if at least h.nativeHistogramMinResetDuration
+// has been passed. It returns true if the histogram has been reset. The caller
+// must have locked h.mtx.
+func (h *histogram) maybeReset(hot, cold *histogramCounts, coldIdx uint64, value float64, bucket int) bool {
+ // We are using the possibly mocked h.now() rather than
+ // time.Since(h.lastResetTime) to enable testing.
+ if h.nativeHistogramMinResetDuration == 0 || h.now().Sub(h.lastResetTime) < h.nativeHistogramMinResetDuration {
+ return false
+ }
+ // Completely reset coldCounts.
+ h.resetCounts(cold)
+ // Repeat the latest observation to not lose it completely.
+ cold.observe(value, bucket, true)
+ // Make coldCounts the new hot counts while ressetting countAndHotIdx.
+ n := atomic.SwapUint64(&h.countAndHotIdx, (coldIdx<<63)+1)
+ count := n & ((1 << 63) - 1)
+ waitForCooldown(count, hot)
+ // Finally, reset the formerly hot counts, too.
+ h.resetCounts(hot)
+ h.lastResetTime = h.now()
+ return true
+}
+
+// maybeWidenZeroBucket widens the zero bucket until it includes the existing
+// buckets closest to the zero bucket (which could be two, if an equidistant
+// negative and a positive bucket exists, but usually it's only one bucket to be
+// merged into the new wider zero bucket). h.nativeHistogramMaxZeroThreshold
+// limits how far the zero bucket can be extended, and if that's not enough to
+// include an existing bucket, the method returns false. The caller must have
+// locked h.mtx.
+func (h *histogram) maybeWidenZeroBucket(hot, cold *histogramCounts) bool {
+ currentZeroThreshold := math.Float64frombits(atomic.LoadUint64(&hot.nativeHistogramZeroThresholdBits))
+ if currentZeroThreshold >= h.nativeHistogramMaxZeroThreshold {
+ return false
+ }
+ // Find the key of the bucket closest to zero.
+ smallestKey := findSmallestKey(&hot.nativeHistogramBucketsPositive)
+ smallestNegativeKey := findSmallestKey(&hot.nativeHistogramBucketsNegative)
+ if smallestNegativeKey < smallestKey {
+ smallestKey = smallestNegativeKey
+ }
+ if smallestKey == math.MaxInt32 {
+ return false
+ }
+ newZeroThreshold := getLe(smallestKey, atomic.LoadInt32(&hot.nativeHistogramSchema))
+ if newZeroThreshold > h.nativeHistogramMaxZeroThreshold {
+ return false // New threshold would exceed the max threshold.
+ }
+ atomic.StoreUint64(&cold.nativeHistogramZeroThresholdBits, math.Float64bits(newZeroThreshold))
+ // Remove applicable buckets.
+ if _, loaded := cold.nativeHistogramBucketsNegative.LoadAndDelete(smallestKey); loaded {
+ atomicDecUint32(&cold.nativeHistogramBucketsNumber)
+ }
+ if _, loaded := cold.nativeHistogramBucketsPositive.LoadAndDelete(smallestKey); loaded {
+ atomicDecUint32(&cold.nativeHistogramBucketsNumber)
+ }
+ // Make cold counts the new hot counts.
+ n := atomic.AddUint64(&h.countAndHotIdx, 1<<63)
+ count := n & ((1 << 63) - 1)
+ // Swap the pointer names to represent the new roles and make
+ // the rest less confusing.
+ hot, cold = cold, hot
+ waitForCooldown(count, cold)
+ // Add all the now cold counts to the new hot counts...
+ addAndResetCounts(hot, cold)
+ // ...adjust the new zero threshold in the cold counts, too...
+ atomic.StoreUint64(&cold.nativeHistogramZeroThresholdBits, math.Float64bits(newZeroThreshold))
+ // ...and then merge the newly deleted buckets into the wider zero
+ // bucket.
+ mergeAndDeleteOrAddAndReset := func(hotBuckets, coldBuckets *sync.Map) func(k, v interface{}) bool {
+ return func(k, v interface{}) bool {
+ key := k.(int)
+ bucket := v.(*int64)
+ if key == smallestKey {
+ // Merge into hot zero bucket...
+ atomic.AddUint64(&hot.nativeHistogramZeroBucket, uint64(atomic.LoadInt64(bucket)))
+ // ...and delete from cold counts.
+ coldBuckets.Delete(key)
+ atomicDecUint32(&cold.nativeHistogramBucketsNumber)
+ } else {
+ // Add to corresponding hot bucket...
+ if addToBucket(hotBuckets, key, atomic.LoadInt64(bucket)) {
+ atomic.AddUint32(&hot.nativeHistogramBucketsNumber, 1)
+ }
+ // ...and reset cold bucket.
+ atomic.StoreInt64(bucket, 0)
+ }
+ return true
}
}
- // Increment count last as we take it as a signal that the observation
- // is complete.
- atomic.AddUint64(&hotCounts.count, 1)
+
+ cold.nativeHistogramBucketsPositive.Range(mergeAndDeleteOrAddAndReset(&hot.nativeHistogramBucketsPositive, &cold.nativeHistogramBucketsPositive))
+ cold.nativeHistogramBucketsNegative.Range(mergeAndDeleteOrAddAndReset(&hot.nativeHistogramBucketsNegative, &cold.nativeHistogramBucketsNegative))
+ return true
+}
+
+// doubleBucketWidth doubles the bucket width (by decrementing the schema
+// number). Note that very sparse buckets could lead to a low reduction of the
+// bucket count (or even no reduction at all). The method does nothing if the
+// schema is already -4.
+func (h *histogram) doubleBucketWidth(hot, cold *histogramCounts) {
+ coldSchema := atomic.LoadInt32(&cold.nativeHistogramSchema)
+ if coldSchema == -4 {
+ return // Already at lowest resolution.
+ }
+ coldSchema--
+ atomic.StoreInt32(&cold.nativeHistogramSchema, coldSchema)
+ // Play it simple and just delete all cold buckets.
+ atomic.StoreUint32(&cold.nativeHistogramBucketsNumber, 0)
+ deleteSyncMap(&cold.nativeHistogramBucketsNegative)
+ deleteSyncMap(&cold.nativeHistogramBucketsPositive)
+ // Make coldCounts the new hot counts.
+ n := atomic.AddUint64(&h.countAndHotIdx, 1<<63)
+ count := n & ((1 << 63) - 1)
+ // Swap the pointer names to represent the new roles and make
+ // the rest less confusing.
+ hot, cold = cold, hot
+ waitForCooldown(count, cold)
+ // Add all the now cold counts to the new hot counts...
+ addAndResetCounts(hot, cold)
+ // ...adjust the schema in the cold counts, too...
+ atomic.StoreInt32(&cold.nativeHistogramSchema, coldSchema)
+ // ...and then merge the cold buckets into the wider hot buckets.
+ merge := func(hotBuckets *sync.Map) func(k, v interface{}) bool {
+ return func(k, v interface{}) bool {
+ key := k.(int)
+ bucket := v.(*int64)
+ // Adjust key to match the bucket to merge into.
+ if key > 0 {
+ key++
+ }
+ key /= 2
+ // Add to corresponding hot bucket.
+ if addToBucket(hotBuckets, key, atomic.LoadInt64(bucket)) {
+ atomic.AddUint32(&hot.nativeHistogramBucketsNumber, 1)
+ }
+ return true
+ }
+ }
+
+ cold.nativeHistogramBucketsPositive.Range(merge(&hot.nativeHistogramBucketsPositive))
+ cold.nativeHistogramBucketsNegative.Range(merge(&hot.nativeHistogramBucketsNegative))
+ // Play it simple again and just delete all cold buckets.
+ atomic.StoreUint32(&cold.nativeHistogramBucketsNumber, 0)
+ deleteSyncMap(&cold.nativeHistogramBucketsNegative)
+ deleteSyncMap(&cold.nativeHistogramBucketsPositive)
+}
+
+func (h *histogram) resetCounts(counts *histogramCounts) {
+ atomic.StoreUint64(&counts.sumBits, 0)
+ atomic.StoreUint64(&counts.count, 0)
+ atomic.StoreUint64(&counts.nativeHistogramZeroBucket, 0)
+ atomic.StoreUint64(&counts.nativeHistogramZeroThresholdBits, math.Float64bits(h.nativeHistogramZeroThreshold))
+ atomic.StoreInt32(&counts.nativeHistogramSchema, h.nativeHistogramSchema)
+ atomic.StoreUint32(&counts.nativeHistogramBucketsNumber, 0)
+ for i := range h.upperBounds {
+ atomic.StoreUint64(&counts.buckets[i], 0)
+ }
+ deleteSyncMap(&counts.nativeHistogramBucketsNegative)
+ deleteSyncMap(&counts.nativeHistogramBucketsPositive)
}
// updateExemplar replaces the exemplar for the provided bucket. With empty
@@ -516,7 +1102,8 @@ func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
// WithLabelValues works as GetMetricWithLabelValues, but panics where
// GetMetricWithLabelValues would have returned an error. Not returning an
// error allows shortcuts like
-// myVec.WithLabelValues("404", "GET").Observe(42.21)
+//
+// myVec.WithLabelValues("404", "GET").Observe(42.21)
func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
h, err := v.GetMetricWithLabelValues(lvs...)
if err != nil {
@@ -527,7 +1114,8 @@ func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
// 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)
+//
+// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
func (v *HistogramVec) With(labels Labels) Observer {
h, err := v.GetMetricWith(labels)
if err != nil {
@@ -581,11 +1169,11 @@ func (h *constHistogram) Desc() *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),
@@ -613,7 +1201,7 @@ func (h *constHistogram) Write(out *dto.Metric) error {
// to send it to Prometheus in the Collect method.
//
// buckets is a map of upper bounds to cumulative counts, excluding the +Inf
-// bucket.
+// bucket. The +Inf bucket is implicit, and its value is equal to the provided count.
//
// NewConstHistogram returns an error if the length of labelValues is not
// consistent with the variable labels in Desc or if Desc is invalid.
@@ -668,3 +1256,229 @@ func (s buckSort) Swap(i, j int) {
func (s buckSort) Less(i, j int) bool {
return s[i].GetUpperBound() < s[j].GetUpperBound()
}
+
+// pickSchema returns the largest number n between -4 and 8 such that
+// 2^(2^-n) is less or equal the provided bucketFactor.
+//
+// Special cases:
+// - bucketFactor <= 1: panics.
+// - bucketFactor < 2^(2^-8) (but > 1): still returns 8.
+func pickSchema(bucketFactor float64) int32 {
+ if bucketFactor <= 1 {
+ panic(fmt.Errorf("bucketFactor %f is <=1", bucketFactor))
+ }
+ floor := math.Floor(math.Log2(math.Log2(bucketFactor)))
+ switch {
+ case floor <= -8:
+ return 8
+ case floor >= 4:
+ return -4
+ default:
+ return -int32(floor)
+ }
+}
+
+func makeBuckets(buckets *sync.Map) ([]*dto.BucketSpan, []int64) {
+ var ii []int
+ buckets.Range(func(k, v interface{}) bool {
+ ii = append(ii, k.(int))
+ return true
+ })
+ sort.Ints(ii)
+
+ if len(ii) == 0 {
+ return nil, nil
+ }
+
+ 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 {
+ v, _ := buckets.Load(i)
+ count := atomic.LoadInt64(v.(*int64))
+ // 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 ore 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
+}
+
+// addToBucket increments the sparse bucket at key by the provided amount. It
+// returns true if a new sparse bucket had to be created for that.
+func addToBucket(buckets *sync.Map, key int, increment int64) bool {
+ if existingBucket, ok := buckets.Load(key); ok {
+ // Fast path without allocation.
+ atomic.AddInt64(existingBucket.(*int64), increment)
+ return false
+ }
+ // Bucket doesn't exist yet. Slow path allocating new counter.
+ newBucket := increment // TODO(beorn7): Check if this is sufficient to not let increment escape.
+ if actualBucket, loaded := buckets.LoadOrStore(key, &newBucket); loaded {
+ // The bucket was created concurrently in another goroutine.
+ // Have to increment after all.
+ atomic.AddInt64(actualBucket.(*int64), increment)
+ return false
+ }
+ return true
+}
+
+// addAndReset returns a function to be used with sync.Map.Range of spare
+// buckets in coldCounts. It increments the buckets in the provided hotBuckets
+// according to the buckets ranged through. It then resets all buckets ranged
+// through to 0 (but leaves them in place so that they don't need to get
+// recreated on the next scrape).
+func addAndReset(hotBuckets *sync.Map, bucketNumber *uint32) func(k, v interface{}) bool {
+ return func(k, v interface{}) bool {
+ bucket := v.(*int64)
+ if addToBucket(hotBuckets, k.(int), atomic.LoadInt64(bucket)) {
+ atomic.AddUint32(bucketNumber, 1)
+ }
+ atomic.StoreInt64(bucket, 0)
+ return true
+ }
+}
+
+func deleteSyncMap(m *sync.Map) {
+ m.Range(func(k, v interface{}) bool {
+ m.Delete(k)
+ return true
+ })
+}
+
+func findSmallestKey(m *sync.Map) int {
+ result := math.MaxInt32
+ m.Range(func(k, v interface{}) bool {
+ key := k.(int)
+ if key < result {
+ result = key
+ }
+ return true
+ })
+ return result
+}
+
+func getLe(key int, schema int32) float64 {
+ // Here a bit of context about the behavior for the last bucket counting
+ // regular numbers (called simply "last bucket" below) and the bucket
+ // counting observations of ±Inf (called "inf bucket" below, with a key
+ // one higher than that of the "last bucket"):
+ //
+ // If we apply the usual formula to the last bucket, its upper bound
+ // would be calculated as +Inf. The reason is that the max possible
+ // regular float64 number (math.MaxFloat64) doesn't coincide with one of
+ // the calculated bucket boundaries. So the calculated boundary has to
+ // be larger than math.MaxFloat64, and the only float64 larger than
+ // math.MaxFloat64 is +Inf. However, we want to count actual
+ // observations of ±Inf in the inf bucket. Therefore, we have to treat
+ // the upper bound of the last bucket specially and set it to
+ // math.MaxFloat64. (The upper bound of the inf bucket, with its key
+ // being one higher than that of the last bucket, naturally comes out as
+ // +Inf by the usual formula. So that's fine.)
+ //
+ // math.MaxFloat64 has a frac of 0.9999999999999999 and an exp of
+ // 1024. If there were a float64 number following math.MaxFloat64, it
+ // would have a frac of 1.0 and an exp of 1024, or equivalently a frac
+ // of 0.5 and an exp of 1025. However, since frac must be smaller than
+ // 1, and exp must be smaller than 1025, either representation overflows
+ // a float64. (Which, in turn, is the reason that math.MaxFloat64 is the
+ // largest possible float64. Q.E.D.) However, the formula for
+ // calculating the upper bound from the idx and schema of the last
+ // bucket results in precisely that. It is either frac=1.0 & exp=1024
+ // (for schema < 0) or frac=0.5 & exp=1025 (for schema >=0). (This is,
+ // by the way, a power of two where the exponent itself is a power of
+ // two, 2¹⁰ in fact, which coinicides with a bucket boundary in all
+ // schemas.) So these are the special cases we have to catch below.
+ if schema < 0 {
+ exp := key << -schema
+ if exp == 1024 {
+ // This is the last bucket before the overflow bucket
+ // (for ±Inf observations). Return math.MaxFloat64 as
+ // explained above.
+ return math.MaxFloat64
+ }
+ return math.Ldexp(1, exp)
+ }
+
+ fracIdx := key & ((1 << schema) - 1)
+ frac := nativeHistogramBounds[schema][fracIdx]
+ exp := (key >> schema) + 1
+ if frac == 0.5 && exp == 1025 {
+ // This is the last bucket before the overflow bucket (for ±Inf
+ // observations). Return math.MaxFloat64 as explained above.
+ return math.MaxFloat64
+ }
+ return math.Ldexp(frac, exp)
+}
+
+// waitForCooldown returns after the count field in the provided histogramCounts
+// has reached the provided count value.
+func waitForCooldown(count uint64, counts *histogramCounts) {
+ for count != atomic.LoadUint64(&counts.count) {
+ runtime.Gosched() // Let observations get work done.
+ }
+}
+
+// atomicAddFloat adds the provided float atomically to another float
+// represented by the bit pattern the bits pointer is pointing to.
+func atomicAddFloat(bits *uint64, v float64) {
+ for {
+ loadedBits := atomic.LoadUint64(bits)
+ newBits := math.Float64bits(math.Float64frombits(loadedBits) + v)
+ if atomic.CompareAndSwapUint64(bits, loadedBits, newBits) {
+ break
+ }
+ }
+}
+
+// atomicDecUint32 atomically decrements the uint32 p points to. See
+// https://pkg.go.dev/sync/atomic#AddUint32 to understand how this is done.
+func atomicDecUint32(p *uint32) {
+ atomic.AddUint32(p, ^uint32(0))
+}
+
+// addAndResetCounts adds certain fields (count, sum, conventional buckets, zero
+// bucket) from the cold counts to the corresponding fields in the hot
+// counts. Those fields are then reset to 0 in the cold counts.
+func addAndResetCounts(hot, cold *histogramCounts) {
+ atomic.AddUint64(&hot.count, atomic.LoadUint64(&cold.count))
+ atomic.StoreUint64(&cold.count, 0)
+ coldSum := math.Float64frombits(atomic.LoadUint64(&cold.sumBits))
+ atomicAddFloat(&hot.sumBits, coldSum)
+ atomic.StoreUint64(&cold.sumBits, 0)
+ for i := range hot.buckets {
+ atomic.AddUint64(&hot.buckets[i], atomic.LoadUint64(&cold.buckets[i]))
+ atomic.StoreUint64(&cold.buckets[i], 0)
+ }
+ atomic.AddUint64(&hot.nativeHistogramZeroBucket, atomic.LoadUint64(&cold.nativeHistogramZeroBucket))
+ atomic.StoreUint64(&cold.nativeHistogramZeroBucket, 0)
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/internal/almost_equal.go b/vendor/github.com/prometheus/client_golang/prometheus/internal/almost_equal.go
new file mode 100644
index 000000000..1ed5abe74
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/internal/almost_equal.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2015 Björn Rabenstein
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+// The code in this package is copy/paste to avoid a dependency. Hence this file
+// carries the copyright of the original repo.
+// https://github.com/beorn7/floats
+package internal
+
+import (
+ "math"
+)
+
+// minNormalFloat64 is the smallest positive normal value of type float64.
+var minNormalFloat64 = math.Float64frombits(0x0010000000000000)
+
+// AlmostEqualFloat64 returns true if a and b are equal within a relative error
+// of epsilon. See http://floating-point-gui.de/errors/comparison/ for the
+// details of the applied method.
+func AlmostEqualFloat64(a, b, epsilon float64) bool {
+ if a == b {
+ return true
+ }
+ absA := math.Abs(a)
+ absB := math.Abs(b)
+ diff := math.Abs(a - b)
+ if a == 0 || b == 0 || absA+absB < minNormalFloat64 {
+ return diff < epsilon*minNormalFloat64
+ }
+ return diff/math.Min(absA+absB, math.MaxFloat64) < epsilon
+}
+
+// AlmostEqualFloat64s is the slice form of AlmostEqualFloat64.
+func AlmostEqualFloat64s(a, b []float64, epsilon float64) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i := range a {
+ if !AlmostEqualFloat64(a[i], b[i], epsilon) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go b/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go
new file mode 100644
index 000000000..fd0750f2c
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/internal/difflib.go
@@ -0,0 +1,654 @@
+// Copyright 2022 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.
+//
+// It provides tools to compare sequences of strings and generate textual diffs.
+//
+// Maintaining `GetUnifiedDiffString` here because original repository
+// (https://github.com/pmezard/go-difflib) is no loger maintained.
+package internal
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "strings"
+)
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func max(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+func calculateRatio(matches, length int) float64 {
+ if length > 0 {
+ return 2.0 * float64(matches) / float64(length)
+ }
+ return 1.0
+}
+
+type Match struct {
+ A int
+ B int
+ Size int
+}
+
+type OpCode struct {
+ Tag byte
+ I1 int
+ I2 int
+ J1 int
+ J2 int
+}
+
+// SequenceMatcher compares sequence of strings. The basic
+// algorithm predates, and is a little fancier than, an algorithm
+// published in the late 1980's by Ratcliff and Obershelp under the
+// hyperbolic name "gestalt pattern matching". The basic idea is to find
+// the longest contiguous matching subsequence that contains no "junk"
+// elements (R-O doesn't address junk). The same idea is then applied
+// recursively to the pieces of the sequences to the left and to the right
+// of the matching subsequence. This does not yield minimal edit
+// sequences, but does tend to yield matches that "look right" to people.
+//
+// SequenceMatcher tries to compute a "human-friendly diff" between two
+// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
+// longest *contiguous* & junk-free matching subsequence. That's what
+// catches peoples' eyes. The Windows(tm) windiff has another interesting
+// notion, pairing up elements that appear uniquely in each sequence.
+// That, and the method here, appear to yield more intuitive difference
+// reports than does diff. This method appears to be the least vulnerable
+// to synching up on blocks of "junk lines", though (like blank lines in
+// ordinary text files, or maybe "<P>" lines in HTML files). That may be
+// because this is the only method of the 3 that has a *concept* of
+// "junk" <wink>.
+//
+// Timing: Basic R-O is cubic time worst case and quadratic time expected
+// case. SequenceMatcher is quadratic time for the worst case and has
+// expected-case behavior dependent in a complicated way on how many
+// elements the sequences have in common; best case time is linear.
+type SequenceMatcher struct {
+ a []string
+ b []string
+ b2j map[string][]int
+ IsJunk func(string) bool
+ autoJunk bool
+ bJunk map[string]struct{}
+ matchingBlocks []Match
+ fullBCount map[string]int
+ bPopular map[string]struct{}
+ opCodes []OpCode
+}
+
+func NewMatcher(a, b []string) *SequenceMatcher {
+ m := SequenceMatcher{autoJunk: true}
+ m.SetSeqs(a, b)
+ return &m
+}
+
+func NewMatcherWithJunk(a, b []string, autoJunk bool,
+ isJunk func(string) bool,
+) *SequenceMatcher {
+ m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
+ m.SetSeqs(a, b)
+ return &m
+}
+
+// Set two sequences to be compared.
+func (m *SequenceMatcher) SetSeqs(a, b []string) {
+ m.SetSeq1(a)
+ m.SetSeq2(b)
+}
+
+// Set the first sequence to be compared. The second sequence to be compared is
+// not changed.
+//
+// SequenceMatcher computes and caches detailed information about the second
+// sequence, so if you want to compare one sequence S against many sequences,
+// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
+// sequences.
+//
+// See also SetSeqs() and SetSeq2().
+func (m *SequenceMatcher) SetSeq1(a []string) {
+ if &a == &m.a {
+ return
+ }
+ m.a = a
+ m.matchingBlocks = nil
+ m.opCodes = nil
+}
+
+// Set the second sequence to be compared. The first sequence to be compared is
+// not changed.
+func (m *SequenceMatcher) SetSeq2(b []string) {
+ if &b == &m.b {
+ return
+ }
+ m.b = b
+ m.matchingBlocks = nil
+ m.opCodes = nil
+ m.fullBCount = nil
+ m.chainB()
+}
+
+func (m *SequenceMatcher) chainB() {
+ // Populate line -> index mapping
+ b2j := map[string][]int{}
+ for i, s := range m.b {
+ indices := b2j[s]
+ indices = append(indices, i)
+ b2j[s] = indices
+ }
+
+ // Purge junk elements
+ m.bJunk = map[string]struct{}{}
+ if m.IsJunk != nil {
+ junk := m.bJunk
+ for s := range b2j {
+ if m.IsJunk(s) {
+ junk[s] = struct{}{}
+ }
+ }
+ for s := range junk {
+ delete(b2j, s)
+ }
+ }
+
+ // Purge remaining popular elements
+ popular := map[string]struct{}{}
+ n := len(m.b)
+ if m.autoJunk && n >= 200 {
+ ntest := n/100 + 1
+ for s, indices := range b2j {
+ if len(indices) > ntest {
+ popular[s] = struct{}{}
+ }
+ }
+ for s := range popular {
+ delete(b2j, s)
+ }
+ }
+ m.bPopular = popular
+ m.b2j = b2j
+}
+
+func (m *SequenceMatcher) isBJunk(s string) bool {
+ _, ok := m.bJunk[s]
+ return ok
+}
+
+// Find longest matching block in a[alo:ahi] and b[blo:bhi].
+//
+// If IsJunk is not defined:
+//
+// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
+//
+// alo <= i <= i+k <= ahi
+// blo <= j <= j+k <= bhi
+//
+// and for all (i',j',k') meeting those conditions,
+//
+// k >= k'
+// i <= i'
+// and if i == i', j <= j'
+//
+// In other words, of all maximal matching blocks, return one that
+// starts earliest in a, and of all those maximal matching blocks that
+// start earliest in a, return the one that starts earliest in b.
+//
+// If IsJunk is defined, first the longest matching block is
+// determined as above, but with the additional restriction that no
+// junk element appears in the block. Then that block is extended as
+// far as possible by matching (only) junk elements on both sides. So
+// the resulting block never matches on junk except as identical junk
+// happens to be adjacent to an "interesting" match.
+//
+// If no blocks match, return (alo, blo, 0).
+func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
+ // CAUTION: stripping common prefix or suffix would be incorrect.
+ // E.g.,
+ // ab
+ // acab
+ // Longest matching block is "ab", but if common prefix is
+ // stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
+ // strip, so ends up claiming that ab is changed to acab by
+ // inserting "ca" in the middle. That's minimal but unintuitive:
+ // "it's obvious" that someone inserted "ac" at the front.
+ // Windiff ends up at the same place as diff, but by pairing up
+ // the unique 'b's and then matching the first two 'a's.
+ besti, bestj, bestsize := alo, blo, 0
+
+ // find longest junk-free match
+ // during an iteration of the loop, j2len[j] = length of longest
+ // junk-free match ending with a[i-1] and b[j]
+ j2len := map[int]int{}
+ for i := alo; i != ahi; i++ {
+ // look at all instances of a[i] in b; note that because
+ // b2j has no junk keys, the loop is skipped if a[i] is junk
+ newj2len := map[int]int{}
+ for _, j := range m.b2j[m.a[i]] {
+ // a[i] matches b[j]
+ if j < blo {
+ continue
+ }
+ if j >= bhi {
+ break
+ }
+ k := j2len[j-1] + 1
+ newj2len[j] = k
+ if k > bestsize {
+ besti, bestj, bestsize = i-k+1, j-k+1, k
+ }
+ }
+ j2len = newj2len
+ }
+
+ // Extend the best by non-junk elements on each end. In particular,
+ // "popular" non-junk elements aren't in b2j, which greatly speeds
+ // the inner loop above, but also means "the best" match so far
+ // doesn't contain any junk *or* popular non-junk elements.
+ for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
+ m.a[besti-1] == m.b[bestj-1] {
+ besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
+ }
+ for besti+bestsize < ahi && bestj+bestsize < bhi &&
+ !m.isBJunk(m.b[bestj+bestsize]) &&
+ m.a[besti+bestsize] == m.b[bestj+bestsize] {
+ bestsize++
+ }
+
+ // Now that we have a wholly interesting match (albeit possibly
+ // empty!), we may as well suck up the matching junk on each
+ // side of it too. Can't think of a good reason not to, and it
+ // saves post-processing the (possibly considerable) expense of
+ // figuring out what to do with it. In the case of an empty
+ // interesting match, this is clearly the right thing to do,
+ // because no other kind of match is possible in the regions.
+ for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
+ m.a[besti-1] == m.b[bestj-1] {
+ besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
+ }
+ for besti+bestsize < ahi && bestj+bestsize < bhi &&
+ m.isBJunk(m.b[bestj+bestsize]) &&
+ m.a[besti+bestsize] == m.b[bestj+bestsize] {
+ bestsize++
+ }
+
+ return Match{A: besti, B: bestj, Size: bestsize}
+}
+
+// Return list of triples describing matching subsequences.
+//
+// Each triple is of the form (i, j, n), and means that
+// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
+// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
+// adjacent triples in the list, and the second is not the last triple in the
+// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
+// adjacent equal blocks.
+//
+// The last triple is a dummy, (len(a), len(b), 0), and is the only
+// triple with n==0.
+func (m *SequenceMatcher) GetMatchingBlocks() []Match {
+ if m.matchingBlocks != nil {
+ return m.matchingBlocks
+ }
+
+ var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
+ matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
+ match := m.findLongestMatch(alo, ahi, blo, bhi)
+ i, j, k := match.A, match.B, match.Size
+ if match.Size > 0 {
+ if alo < i && blo < j {
+ matched = matchBlocks(alo, i, blo, j, matched)
+ }
+ matched = append(matched, match)
+ if i+k < ahi && j+k < bhi {
+ matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
+ }
+ }
+ return matched
+ }
+ matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
+
+ // It's possible that we have adjacent equal blocks in the
+ // matching_blocks list now.
+ nonAdjacent := []Match{}
+ i1, j1, k1 := 0, 0, 0
+ for _, b := range matched {
+ // Is this block adjacent to i1, j1, k1?
+ i2, j2, k2 := b.A, b.B, b.Size
+ if i1+k1 == i2 && j1+k1 == j2 {
+ // Yes, so collapse them -- this just increases the length of
+ // the first block by the length of the second, and the first
+ // block so lengthened remains the block to compare against.
+ k1 += k2
+ } else {
+ // Not adjacent. Remember the first block (k1==0 means it's
+ // the dummy we started with), and make the second block the
+ // new block to compare against.
+ if k1 > 0 {
+ nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
+ }
+ i1, j1, k1 = i2, j2, k2
+ }
+ }
+ if k1 > 0 {
+ nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
+ }
+
+ nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
+ m.matchingBlocks = nonAdjacent
+ return m.matchingBlocks
+}
+
+// Return list of 5-tuples describing how to turn a into b.
+//
+// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
+// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
+// tuple preceding it, and likewise for j1 == the previous j2.
+//
+// The tags are characters, with these meanings:
+//
+// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
+//
+// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
+//
+// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
+//
+// 'e' (equal): a[i1:i2] == b[j1:j2]
+func (m *SequenceMatcher) GetOpCodes() []OpCode {
+ if m.opCodes != nil {
+ return m.opCodes
+ }
+ i, j := 0, 0
+ matching := m.GetMatchingBlocks()
+ opCodes := make([]OpCode, 0, len(matching))
+ for _, m := range matching {
+ // invariant: we've pumped out correct diffs to change
+ // a[:i] into b[:j], and the next matching block is
+ // a[ai:ai+size] == b[bj:bj+size]. So we need to pump
+ // out a diff to change a[i:ai] into b[j:bj], pump out
+ // the matching block, and move (i,j) beyond the match
+ ai, bj, size := m.A, m.B, m.Size
+ tag := byte(0)
+ if i < ai && j < bj {
+ tag = 'r'
+ } else if i < ai {
+ tag = 'd'
+ } else if j < bj {
+ tag = 'i'
+ }
+ if tag > 0 {
+ opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
+ }
+ i, j = ai+size, bj+size
+ // the list of matching blocks is terminated by a
+ // sentinel with size 0
+ if size > 0 {
+ opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
+ }
+ }
+ m.opCodes = opCodes
+ return m.opCodes
+}
+
+// Isolate change clusters by eliminating ranges with no changes.
+//
+// Return a generator of groups with up to n lines of context.
+// Each group is in the same format as returned by GetOpCodes().
+func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
+ if n < 0 {
+ n = 3
+ }
+ codes := m.GetOpCodes()
+ if len(codes) == 0 {
+ codes = []OpCode{{'e', 0, 1, 0, 1}}
+ }
+ // Fixup leading and trailing groups if they show no changes.
+ if codes[0].Tag == 'e' {
+ c := codes[0]
+ 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}
+ }
+ if codes[len(codes)-1].Tag == 'e' {
+ c := codes[len(codes)-1]
+ 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)}
+ }
+ nn := n + n
+ groups := [][]OpCode{}
+ group := []OpCode{}
+ for _, c := range codes {
+ i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
+ // End the current group and start a new one whenever
+ // there is a large range with no changes.
+ if c.Tag == 'e' && i2-i1 > nn {
+ group = append(group, OpCode{
+ c.Tag, i1, min(i2, i1+n),
+ j1, min(j2, j1+n),
+ })
+ groups = append(groups, group)
+ group = []OpCode{}
+ i1, j1 = max(i1, i2-n), max(j1, j2-n)
+ }
+ group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
+ }
+ if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
+ groups = append(groups, group)
+ }
+ return groups
+}
+
+// Return a measure of the sequences' similarity (float in [0,1]).
+//
+// Where T is the total number of elements in both sequences, and
+// M is the number of matches, this is 2.0*M / T.
+// Note that this is 1 if the sequences are identical, and 0 if
+// they have nothing in common.
+//
+// .Ratio() is expensive to compute if you haven't already computed
+// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
+// want to try .QuickRatio() or .RealQuickRation() first to get an
+// upper bound.
+func (m *SequenceMatcher) Ratio() float64 {
+ matches := 0
+ for _, m := range m.GetMatchingBlocks() {
+ matches += m.Size
+ }
+ return calculateRatio(matches, len(m.a)+len(m.b))
+}
+
+// Return an upper bound on ratio() relatively quickly.
+//
+// This isn't defined beyond that it is an upper bound on .Ratio(), and
+// is faster to compute.
+func (m *SequenceMatcher) QuickRatio() float64 {
+ // viewing a and b as multisets, set matches to the cardinality
+ // of their intersection; this counts the number of matches
+ // without regard to order, so is clearly an upper bound
+ if m.fullBCount == nil {
+ m.fullBCount = map[string]int{}
+ for _, s := range m.b {
+ m.fullBCount[s]++
+ }
+ }
+
+ // avail[x] is the number of times x appears in 'b' less the
+ // number of times we've seen it in 'a' so far ... kinda
+ avail := map[string]int{}
+ matches := 0
+ for _, s := range m.a {
+ n, ok := avail[s]
+ if !ok {
+ n = m.fullBCount[s]
+ }
+ avail[s] = n - 1
+ if n > 0 {
+ matches++
+ }
+ }
+ return calculateRatio(matches, len(m.a)+len(m.b))
+}
+
+// Return an upper bound on ratio() very quickly.
+//
+// This isn't defined beyond that it is an upper bound on .Ratio(), and
+// is faster to compute than either .Ratio() or .QuickRatio().
+func (m *SequenceMatcher) RealQuickRatio() float64 {
+ la, lb := len(m.a), len(m.b)
+ return calculateRatio(min(la, lb), la+lb)
+}
+
+// Convert range to the "ed" format
+func formatRangeUnified(start, stop int) string {
+ // Per the diff spec at http://www.unix.org/single_unix_specification/
+ beginning := start + 1 // lines start numbering with one
+ length := stop - start
+ if length == 1 {
+ return fmt.Sprintf("%d", beginning)
+ }
+ if length == 0 {
+ beginning-- // empty ranges begin at line just before the range
+ }
+ return fmt.Sprintf("%d,%d", beginning, length)
+}
+
+// Unified diff parameters
+type UnifiedDiff struct {
+ A []string // First sequence lines
+ FromFile string // First file name
+ FromDate string // First file time
+ B []string // Second sequence lines
+ ToFile string // Second file name
+ ToDate string // Second file time
+ Eol string // Headers end of line, defaults to LF
+ Context int // Number of context lines
+}
+
+// Compare two sequences of lines; generate the delta as a unified diff.
+//
+// Unified diffs are a compact way of showing line changes and a few
+// lines of context. The number of context lines is set by 'n' which
+// defaults to three.
+//
+// By default, the diff control lines (those with ---, +++, or @@) are
+// created with a trailing newline. This is helpful so that inputs
+// created from file.readlines() result in diffs that are suitable for
+// file.writelines() since both the inputs and outputs have trailing
+// newlines.
+//
+// For inputs that do not have trailing newlines, set the lineterm
+// argument to "" so that the output will be uniformly newline free.
+//
+// The unidiff format normally has a header for filenames and modification
+// times. Any or all of these may be specified using strings for
+// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
+// The modification times are normally expressed in the ISO 8601 format.
+func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
+ buf := bufio.NewWriter(writer)
+ defer buf.Flush()
+ wf := func(format string, args ...interface{}) error {
+ _, err := buf.WriteString(fmt.Sprintf(format, args...))
+ return err
+ }
+ ws := func(s string) error {
+ _, err := buf.WriteString(s)
+ return err
+ }
+
+ if len(diff.Eol) == 0 {
+ diff.Eol = "\n"
+ }
+
+ started := false
+ m := NewMatcher(diff.A, diff.B)
+ for _, g := range m.GetGroupedOpCodes(diff.Context) {
+ if !started {
+ started = true
+ fromDate := ""
+ if len(diff.FromDate) > 0 {
+ fromDate = "\t" + diff.FromDate
+ }
+ toDate := ""
+ if len(diff.ToDate) > 0 {
+ toDate = "\t" + diff.ToDate
+ }
+ if diff.FromFile != "" || diff.ToFile != "" {
+ err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
+ if err != nil {
+ return err
+ }
+ err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ first, last := g[0], g[len(g)-1]
+ range1 := formatRangeUnified(first.I1, last.I2)
+ range2 := formatRangeUnified(first.J1, last.J2)
+ if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
+ return err
+ }
+ for _, c := range g {
+ i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
+ if c.Tag == 'e' {
+ for _, line := range diff.A[i1:i2] {
+ if err := ws(" " + line); err != nil {
+ return err
+ }
+ }
+ continue
+ }
+ if c.Tag == 'r' || c.Tag == 'd' {
+ for _, line := range diff.A[i1:i2] {
+ if err := ws("-" + line); err != nil {
+ return err
+ }
+ }
+ }
+ if c.Tag == 'r' || c.Tag == 'i' {
+ for _, line := range diff.B[j1:j2] {
+ if err := ws("+" + line); err != nil {
+ return err
+ }
+ }
+ }
+ }
+ }
+ return nil
+}
+
+// Like WriteUnifiedDiff but returns the diff a string.
+func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
+ w := &bytes.Buffer{}
+ err := WriteUnifiedDiff(w, diff)
+ return w.String(), err
+}
+
+// Split a string on "\n" while preserving them. The output can be used
+// as input for UnifiedDiff and ContextDiff structures.
+func SplitLines(s string) []string {
+ lines := strings.SplitAfter(s, "\n")
+ lines[len(lines)-1] += "\n"
+ return lines
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go b/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go
new file mode 100644
index 000000000..723b45d64
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go
@@ -0,0 +1,32 @@
+// Copyright 2021 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 internal
+
+import "regexp"
+
+type GoCollectorRule struct {
+ Matcher *regexp.Regexp
+ Deny bool
+}
+
+// GoCollectorOptions should not be used be directly by anything, except `collectors` package.
+// Use it via collectors package instead. See issue
+// https://github.com/prometheus/client_golang/issues/1030.
+//
+// This is internal, so external users only can use it via `collector.WithGoCollector*` methods
+type GoCollectorOptions struct {
+ DisableMemStatsLikeMetrics bool
+ RuntimeMetricSumForHist map[string]string
+ RuntimeMetricRules []GoCollectorRule
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/internal/go_runtime_metrics.go b/vendor/github.com/prometheus/client_golang/prometheus/internal/go_runtime_metrics.go
index fe0a52180..97d17d6cb 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/internal/go_runtime_metrics.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/internal/go_runtime_metrics.go
@@ -61,9 +61,9 @@ func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool)
// name has - replaced with _ and is concatenated with the unit and
// other data.
name = strings.ReplaceAll(name, "-", "_")
- name = name + "_" + unit
- if d.Cumulative {
- name = name + "_total"
+ name += "_" + unit
+ if d.Cumulative && d.Kind != metrics.KindFloat64Histogram {
+ name += "_total"
}
valid := model.IsValidMetricName(model.LabelValue(namespace + "_" + subsystem + "_" + name))
@@ -84,12 +84,12 @@ func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool)
func RuntimeMetricsBucketsForUnit(buckets []float64, unit string) []float64 {
switch unit {
case "bytes":
- // Rebucket as powers of 2.
- return rebucketExp(buckets, 2)
+ // Re-bucket as powers of 2.
+ return reBucketExp(buckets, 2)
case "seconds":
- // Rebucket as powers of 10 and then merge all buckets greater
+ // Re-bucket as powers of 10 and then merge all buckets greater
// than 1 second into the +Inf bucket.
- b := rebucketExp(buckets, 10)
+ b := reBucketExp(buckets, 10)
for i := range b {
if b[i] <= 1 {
continue
@@ -103,11 +103,11 @@ func RuntimeMetricsBucketsForUnit(buckets []float64, unit string) []float64 {
return buckets
}
-// rebucketExp takes a list of bucket boundaries (lower bound inclusive) and
+// reBucketExp takes a list of bucket boundaries (lower bound inclusive) and
// downsamples the buckets to those a multiple of base apart. The end result
// is a roughly exponential (in many cases, perfectly exponential) bucketing
// scheme.
-func rebucketExp(buckets []float64, base float64) []float64 {
+func reBucketExp(buckets []float64, base float64) []float64 {
bucket := buckets[0]
var newBuckets []float64
// We may see a -Inf here, in which case, add it and skip it
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/internal/metric.go b/vendor/github.com/prometheus/client_golang/prometheus/internal/metric.go
index 351c26e1a..6515c1148 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/internal/metric.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/internal/metric.go
@@ -19,18 +19,34 @@ import (
dto "github.com/prometheus/client_model/go"
)
-// metricSorter is a sortable slice of *dto.Metric.
-type metricSorter []*dto.Metric
+// LabelPairSorter implements sort.Interface. It is used to sort a slice of
+// dto.LabelPair pointers.
+type LabelPairSorter []*dto.LabelPair
-func (s metricSorter) Len() int {
+func (s LabelPairSorter) Len() int {
return len(s)
}
-func (s metricSorter) Swap(i, j int) {
+func (s LabelPairSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
-func (s metricSorter) Less(i, j int) bool {
+func (s LabelPairSorter) Less(i, j int) bool {
+ return s[i].GetName() < s[j].GetName()
+}
+
+// MetricSorter is a sortable slice of *dto.Metric.
+type MetricSorter []*dto.Metric
+
+func (s MetricSorter) Len() int {
+ return len(s)
+}
+
+func (s MetricSorter) Swap(i, j int) {
+ s[i], s[j] = s[j], s[i]
+}
+
+func (s MetricSorter) Less(i, j int) bool {
if len(s[i].Label) != len(s[j].Label) {
// This should not happen. The metrics are
// inconsistent. However, we have to deal with the fact, as
@@ -68,7 +84,7 @@ func (s metricSorter) Less(i, j int) bool {
// the slice, with the contained Metrics sorted within each MetricFamily.
func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
for _, mf := range metricFamiliesByName {
- sort.Sort(metricSorter(mf.Metric))
+ sort.Sort(MetricSorter(mf.Metric))
}
names := make([]string, 0, len(metricFamiliesByName))
for name, mf := range metricFamiliesByName {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/labels.go b/vendor/github.com/prometheus/client_golang/prometheus/labels.go
index 2744443ac..c1b8fad36 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/labels.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/labels.go
@@ -25,7 +25,8 @@ import (
// Labels represents a collection of label name -> value mappings. This type is
// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
// metric vector Collectors, e.g.:
-// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
+//
+// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
//
// The other use-case is the specification of constant label pairs in Opts or to
// create a Desc.
@@ -39,7 +40,7 @@ var errInconsistentCardinality = errors.New("inconsistent label cardinality")
func makeInconsistentCardinalityError(fqName string, labels, labelValues []string) error {
return fmt.Errorf(
- "%s: %q has %d variable labels named %q but %d values %q were provided",
+ "%w: %q has %d variable labels named %q but %d values %q were provided",
errInconsistentCardinality, fqName,
len(labels), labels,
len(labelValues), labelValues,
@@ -49,7 +50,7 @@ func makeInconsistentCardinalityError(fqName string, labels, labelValues []strin
func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
if len(labels) != expectedNumberOfValues {
return fmt.Errorf(
- "%s: expected %d label values but got %d in %#v",
+ "%w: expected %d label values but got %d in %#v",
errInconsistentCardinality, expectedNumberOfValues,
len(labels), labels,
)
@@ -67,7 +68,7 @@ func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
func validateLabelValues(vals []string, expectedNumberOfValues int) error {
if len(vals) != expectedNumberOfValues {
return fmt.Errorf(
- "%s: expected %d label values but got %d in %#v",
+ "%w: expected %d label values but got %d in %#v",
errInconsistentCardinality, expectedNumberOfValues,
len(vals), vals,
)
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/metric.go b/vendor/github.com/prometheus/client_golang/prometheus/metric.go
index dc121910a..b5119c504 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/metric.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/metric.go
@@ -14,6 +14,9 @@
package prometheus
import (
+ "errors"
+ "math"
+ "sort"
"strings"
"time"
@@ -115,22 +118,6 @@ func BuildFQName(namespace, subsystem, name string) string {
return name
}
-// labelPairSorter implements sort.Interface. It is used to sort a slice of
-// dto.LabelPair pointers.
-type labelPairSorter []*dto.LabelPair
-
-func (s labelPairSorter) Len() int {
- return len(s)
-}
-
-func (s labelPairSorter) Swap(i, j int) {
- s[i], s[j] = s[j], s[i]
-}
-
-func (s labelPairSorter) Less(i, j int) bool {
- return s[i].GetName() < s[j].GetName()
-}
-
type invalidMetric struct {
desc *Desc
err error
@@ -174,3 +161,96 @@ func (m timestampedMetric) Write(pb *dto.Metric) error {
func NewMetricWithTimestamp(t time.Time, m Metric) Metric {
return timestampedMetric{Metric: m, t: t}
}
+
+type withExemplarsMetric struct {
+ Metric
+
+ exemplars []*dto.Exemplar
+}
+
+func (m *withExemplarsMetric) Write(pb *dto.Metric) error {
+ if err := m.Metric.Write(pb); err != nil {
+ return err
+ }
+
+ switch {
+ case pb.Counter != nil:
+ pb.Counter.Exemplar = m.exemplars[len(m.exemplars)-1]
+ case pb.Histogram != nil:
+ for _, e := range m.exemplars {
+ // pb.Histogram.Bucket are sorted by UpperBound.
+ i := sort.Search(len(pb.Histogram.Bucket), func(i int) bool {
+ return pb.Histogram.Bucket[i].GetUpperBound() >= e.GetValue()
+ })
+ if i < len(pb.Histogram.Bucket) {
+ pb.Histogram.Bucket[i].Exemplar = e
+ } else {
+ // The +Inf bucket should be explicitly added if there is an exemplar for it, similar to non-const histogram logic in https://github.com/prometheus/client_golang/blob/main/prometheus/histogram.go#L357-L365.
+ b := &dto.Bucket{
+ CumulativeCount: proto.Uint64(pb.Histogram.GetSampleCount()),
+ UpperBound: proto.Float64(math.Inf(1)),
+ Exemplar: e,
+ }
+ pb.Histogram.Bucket = append(pb.Histogram.Bucket, b)
+ }
+ }
+ default:
+ // TODO(bwplotka): Implement Gauge?
+ return errors.New("cannot inject exemplar into Gauge, Summary or Untyped")
+ }
+
+ return nil
+}
+
+// Exemplar is easier to use, user-facing representation of *dto.Exemplar.
+type Exemplar struct {
+ Value float64
+ Labels Labels
+ // Optional.
+ // Default value (time.Time{}) indicates its empty, which should be
+ // understood as time.Now() time at the moment of creation of metric.
+ Timestamp time.Time
+}
+
+// NewMetricWithExemplars returns a new Metric wrapping the provided Metric with given
+// exemplars. Exemplars are validated.
+//
+// Only last applicable exemplar is injected from the list.
+// For example for Counter it means last exemplar is injected.
+// For Histogram, it means last applicable exemplar for each bucket is injected.
+//
+// NewMetricWithExemplars works best with MustNewConstMetric and
+// MustNewConstHistogram, see example.
+func NewMetricWithExemplars(m Metric, exemplars ...Exemplar) (Metric, error) {
+ if len(exemplars) == 0 {
+ return nil, errors.New("no exemplar was passed for NewMetricWithExemplars")
+ }
+
+ var (
+ now = time.Now()
+ exs = make([]*dto.Exemplar, len(exemplars))
+ err error
+ )
+ for i, e := range exemplars {
+ ts := e.Timestamp
+ if ts == (time.Time{}) {
+ ts = now
+ }
+ exs[i], err = newExemplar(e.Value, ts, e.Labels)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return &withExemplarsMetric{Metric: m, exemplars: exs}, nil
+}
+
+// MustNewMetricWithExemplars is a version of NewMetricWithExemplars that panics where
+// NewMetricWithExemplars would have returned an error.
+func MustNewMetricWithExemplars(m Metric, exemplars ...Exemplar) Metric {
+ ret, err := NewMetricWithExemplars(m, exemplars...)
+ if err != nil {
+ panic(err)
+ }
+ return ret
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/num_threads.go b/vendor/github.com/prometheus/client_golang/prometheus/num_threads.go
new file mode 100644
index 000000000..7c12b2108
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/num_threads.go
@@ -0,0 +1,25 @@
+// Copyright 2018 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 !js || wasm
+// +build !js wasm
+
+package prometheus
+
+import "runtime"
+
+// getRuntimeNumThreads returns the number of open OS threads.
+func getRuntimeNumThreads() float64 {
+ n, _ := runtime.ThreadCreateProfile(nil)
+ return float64(n)
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/num_threads_gopherjs.go b/vendor/github.com/prometheus/client_golang/prometheus/num_threads_gopherjs.go
new file mode 100644
index 000000000..7348df01d
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/num_threads_gopherjs.go
@@ -0,0 +1,22 @@
+// Copyright 2018 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 js && !wasm
+// +build js,!wasm
+
+package prometheus
+
+// getRuntimeNumThreads returns the number of open OS threads.
+func getRuntimeNumThreads() float64 {
+ return 1
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/observer.go b/vendor/github.com/prometheus/client_golang/prometheus/observer.go
index 44128016f..03773b21f 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/observer.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/observer.go
@@ -58,7 +58,7 @@ type ObserverVec interface {
// current time as timestamp, and the provided Labels. Empty Labels will lead to
// a valid (label-less) exemplar. But if Labels is nil, the current exemplar is
// left in place. ObserveWithExemplar panics if any of the provided labels are
-// invalid or if the provided labels contain more than 64 runes in total.
+// invalid or if the provided labels contain more than 128 runes in total.
type ExemplarObserver interface {
ObserveWithExemplar(value float64, exemplar Labels)
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
index 5bfe0ff5b..8548dd18e 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
@@ -16,7 +16,6 @@ package prometheus
import (
"errors"
"fmt"
- "io/ioutil"
"os"
"strconv"
"strings"
@@ -104,8 +103,7 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector {
}
if opts.PidFn == nil {
- pid := os.Getpid()
- c.pidFn = func() (int, error) { return pid, nil }
+ c.pidFn = getPIDFn()
} else {
c.pidFn = opts.PidFn
}
@@ -152,13 +150,13 @@ func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error)
// It is meant to be used for the PidFn field in ProcessCollectorOpts.
func NewPidFileFn(pidFilePath string) func() (int, error) {
return func() (int, error) {
- content, err := ioutil.ReadFile(pidFilePath)
+ content, err := os.ReadFile(pidFilePath)
if err != nil {
- return 0, fmt.Errorf("can't read pid file %q: %+v", pidFilePath, err)
+ return 0, fmt.Errorf("can't read pid file %q: %w", pidFilePath, err)
}
pid, err := strconv.Atoi(strings.TrimSpace(string(content)))
if err != nil {
- return 0, fmt.Errorf("can't parse pid file %q: %+v", pidFilePath, err)
+ return 0, fmt.Errorf("can't parse pid file %q: %w", pidFilePath, err)
}
return pid, nil
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_js.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_js.go
new file mode 100644
index 000000000..b1e363d6c
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_js.go
@@ -0,0 +1,26 @@
+// Copyright 2019 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 js
+// +build js
+
+package prometheus
+
+func canCollectProcess() bool {
+ return false
+}
+
+func (c *processCollector) processCollect(ch chan<- Metric) {
+ // noop on this platform
+ return
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go
index 2dc3660da..c0152cdb6 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//go:build !windows
-// +build !windows
+//go:build !windows && !js
+// +build !windows,!js
package prometheus
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promauto/auto.go b/vendor/github.com/prometheus/client_golang/prometheus/promauto/auto.go
index f8d50d1f9..8031e8704 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/promauto/auto.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promauto/auto.go
@@ -14,114 +14,114 @@
// Package promauto provides alternative constructors for the fundamental
// Prometheus metric types and their …Vec and …Func variants. The difference to
// their counterparts in the prometheus package is that the promauto
-// constructors return Collectors that are already registered with a
-// registry. There are two sets of constructors. The constructors in the first
-// set are top-level functions, while the constructors in the other set are
-// methods of the Factory type. The top-level function return Collectors
-// registered with the global registry (prometheus.DefaultRegisterer), while the
-// methods return Collectors registered with the registry the Factory was
-// constructed with. All constructors panic if the registration fails.
+// constructors register the Collectors with a registry before returning them.
+// There are two sets of constructors. The constructors in the first set are
+// top-level functions, while the constructors in the other set are methods of
+// the Factory type. The top-level function return Collectors registered with
+// the global registry (prometheus.DefaultRegisterer), while the methods return
+// Collectors registered with the registry the Factory was constructed with. All
+// constructors panic if the registration fails.
//
// The following example is a complete program to create a histogram of normally
// distributed random numbers from the math/rand package:
//
-// package main
+// package main
//
-// import (
-// "math/rand"
-// "net/http"
+// import (
+// "math/rand"
+// "net/http"
//
-// "github.com/prometheus/client_golang/prometheus"
-// "github.com/prometheus/client_golang/prometheus/promauto"
-// "github.com/prometheus/client_golang/prometheus/promhttp"
-// )
+// "github.com/prometheus/client_golang/prometheus"
+// "github.com/prometheus/client_golang/prometheus/promauto"
+// "github.com/prometheus/client_golang/prometheus/promhttp"
+// )
//
-// var histogram = promauto.NewHistogram(prometheus.HistogramOpts{
-// Name: "random_numbers",
-// Help: "A histogram of normally distributed random numbers.",
-// Buckets: prometheus.LinearBuckets(-3, .1, 61),
-// })
+// var histogram = promauto.NewHistogram(prometheus.HistogramOpts{
+// Name: "random_numbers",
+// Help: "A histogram of normally distributed random numbers.",
+// Buckets: prometheus.LinearBuckets(-3, .1, 61),
+// })
//
-// func Random() {
-// for {
-// histogram.Observe(rand.NormFloat64())
-// }
-// }
+// func Random() {
+// for {
+// histogram.Observe(rand.NormFloat64())
+// }
+// }
//
-// func main() {
-// go Random()
-// http.Handle("/metrics", promhttp.Handler())
-// http.ListenAndServe(":1971", nil)
-// }
+// func main() {
+// go Random()
+// http.Handle("/metrics", promhttp.Handler())
+// http.ListenAndServe(":1971", nil)
+// }
//
// Prometheus's version of a minimal hello-world program:
//
-// package main
+// package main
//
-// import (
-// "fmt"
-// "net/http"
+// import (
+// "fmt"
+// "net/http"
//
-// "github.com/prometheus/client_golang/prometheus"
-// "github.com/prometheus/client_golang/prometheus/promauto"
-// "github.com/prometheus/client_golang/prometheus/promhttp"
-// )
+// "github.com/prometheus/client_golang/prometheus"
+// "github.com/prometheus/client_golang/prometheus/promauto"
+// "github.com/prometheus/client_golang/prometheus/promhttp"
+// )
//
-// func main() {
-// http.Handle("/", promhttp.InstrumentHandlerCounter(
-// promauto.NewCounterVec(
-// prometheus.CounterOpts{
-// Name: "hello_requests_total",
-// Help: "Total number of hello-world requests by HTTP code.",
-// },
-// []string{"code"},
-// ),
-// http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-// fmt.Fprint(w, "Hello, world!")
-// }),
-// ))
-// http.Handle("/metrics", promhttp.Handler())
-// http.ListenAndServe(":1971", nil)
-// }
+// func main() {
+// http.Handle("/", promhttp.InstrumentHandlerCounter(
+// promauto.NewCounterVec(
+// prometheus.CounterOpts{
+// Name: "hello_requests_total",
+// Help: "Total number of hello-world requests by HTTP code.",
+// },
+// []string{"code"},
+// ),
+// http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+// fmt.Fprint(w, "Hello, world!")
+// }),
+// ))
+// http.Handle("/metrics", promhttp.Handler())
+// http.ListenAndServe(":1971", nil)
+// }
//
// A Factory is created with the With(prometheus.Registerer) function, which
// enables two usage pattern. With(prometheus.Registerer) can be called once per
// line:
//
-// var (
-// reg = prometheus.NewRegistry()
-// randomNumbers = promauto.With(reg).NewHistogram(prometheus.HistogramOpts{
-// Name: "random_numbers",
-// Help: "A histogram of normally distributed random numbers.",
-// Buckets: prometheus.LinearBuckets(-3, .1, 61),
-// })
-// requestCount = promauto.With(reg).NewCounterVec(
-// prometheus.CounterOpts{
-// Name: "http_requests_total",
-// Help: "Total number of HTTP requests by status code and method.",
-// },
-// []string{"code", "method"},
-// )
-// )
+// var (
+// reg = prometheus.NewRegistry()
+// randomNumbers = promauto.With(reg).NewHistogram(prometheus.HistogramOpts{
+// Name: "random_numbers",
+// Help: "A histogram of normally distributed random numbers.",
+// Buckets: prometheus.LinearBuckets(-3, .1, 61),
+// })
+// requestCount = promauto.With(reg).NewCounterVec(
+// prometheus.CounterOpts{
+// Name: "http_requests_total",
+// Help: "Total number of HTTP requests by status code and method.",
+// },
+// []string{"code", "method"},
+// )
+// )
//
// Or it can be used to create a Factory once to be used multiple times:
//
-// var (
-// reg = prometheus.NewRegistry()
-// factory = promauto.With(reg)
-// randomNumbers = factory.NewHistogram(prometheus.HistogramOpts{
-// Name: "random_numbers",
-// Help: "A histogram of normally distributed random numbers.",
-// Buckets: prometheus.LinearBuckets(-3, .1, 61),
-// })
-// requestCount = factory.NewCounterVec(
-// prometheus.CounterOpts{
-// Name: "http_requests_total",
-// Help: "Total number of HTTP requests by status code and method.",
-// },
-// []string{"code", "method"},
-// )
-// )
+// var (
+// reg = prometheus.NewRegistry()
+// factory = promauto.With(reg)
+// randomNumbers = factory.NewHistogram(prometheus.HistogramOpts{
+// Name: "random_numbers",
+// Help: "A histogram of normally distributed random numbers.",
+// Buckets: prometheus.LinearBuckets(-3, .1, 61),
+// })
+// requestCount = factory.NewCounterVec(
+// prometheus.CounterOpts{
+// Name: "http_requests_total",
+// Help: "Total number of HTTP requests by status code and method.",
+// },
+// []string{"code", "method"},
+// )
+// )
//
// This appears very handy. So why are these constructors locked away in a
// separate package?
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
index e7c0d0546..9819917b8 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
@@ -76,16 +76,19 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) {
return n, err
}
-type closeNotifierDelegator struct{ *responseWriterDelegator }
-type flusherDelegator struct{ *responseWriterDelegator }
-type hijackerDelegator struct{ *responseWriterDelegator }
-type readerFromDelegator struct{ *responseWriterDelegator }
-type pusherDelegator struct{ *responseWriterDelegator }
+type (
+ closeNotifierDelegator struct{ *responseWriterDelegator }
+ flusherDelegator struct{ *responseWriterDelegator }
+ hijackerDelegator struct{ *responseWriterDelegator }
+ readerFromDelegator struct{ *responseWriterDelegator }
+ pusherDelegator struct{ *responseWriterDelegator }
+)
func (d closeNotifierDelegator) CloseNotify() <-chan bool {
//nolint:staticcheck // Ignore SA1019. http.CloseNotifier is deprecated but we keep it here to not break existing users.
return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
+
func (d flusherDelegator) Flush() {
// If applicable, call WriteHeader here so that observeWriteHeader is
// handled appropriately.
@@ -94,9 +97,11 @@ func (d flusherDelegator) Flush() {
}
d.ResponseWriter.(http.Flusher).Flush()
}
+
func (d hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return d.ResponseWriter.(http.Hijacker).Hijack()
}
+
func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
// If applicable, call WriteHeader here so that observeWriteHeader is
// handled appropriately.
@@ -107,6 +112,7 @@ func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
d.written += n
return n, err
}
+
func (d pusherDelegator) Push(target string, opts *http.PushOptions) error {
return d.ResponseWriter.(http.Pusher).Push(target, opts)
}
@@ -261,7 +267,7 @@ func init() {
http.Flusher
}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
}
- pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23
+ pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 23
return struct {
*responseWriterDelegator
http.Pusher
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
index d86d0cf4b..a4cc9810b 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
@@ -33,6 +33,7 @@ package promhttp
import (
"compress/gzip"
+ "errors"
"fmt"
"io"
"net/http"
@@ -84,6 +85,13 @@ func Handler() http.Handler {
// instrumentation. Use the InstrumentMetricHandler function to apply the same
// kind of instrumentation as it is used by the Handler function.
func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
+ return HandlerForTransactional(prometheus.ToTransactionalGatherer(reg), opts)
+}
+
+// HandlerForTransactional is like HandlerFor, but it uses transactional gather, which
+// can safely change in-place returned *dto.MetricFamily before call to `Gather` and after
+// call to `done` of that `Gather`.
+func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerOpts) http.Handler {
var (
inFlightSem chan struct{}
errCnt = prometheus.NewCounterVec(
@@ -103,7 +111,8 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
errCnt.WithLabelValues("gathering")
errCnt.WithLabelValues("encoding")
if err := opts.Registry.Register(errCnt); err != nil {
- if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
+ are := &prometheus.AlreadyRegisteredError{}
+ if errors.As(err, are) {
errCnt = are.ExistingCollector.(*prometheus.CounterVec)
} else {
panic(err)
@@ -123,7 +132,8 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
return
}
}
- mfs, err := reg.Gather()
+ mfs, done, err := reg.Gather()
+ defer done()
if err != nil {
if opts.ErrorLog != nil {
opts.ErrorLog.Println("error gathering metrics:", err)
@@ -242,7 +252,8 @@ func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) ht
cnt.WithLabelValues("500")
cnt.WithLabelValues("503")
if err := reg.Register(cnt); err != nil {
- if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
+ are := &prometheus.AlreadyRegisteredError{}
+ if errors.As(err, are) {
cnt = are.ExistingCollector.(*prometheus.CounterVec)
} else {
panic(err)
@@ -254,7 +265,8 @@ func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) ht
Help: "Current number of scrapes being served.",
})
if err := reg.Register(gge); err != nil {
- if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
+ are := &prometheus.AlreadyRegisteredError{}
+ if errors.As(err, are) {
gge = are.ExistingCollector.(prometheus.Gauge)
} else {
panic(err)
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
index 861b4d21c..210867816 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
@@ -38,11 +38,11 @@ func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
//
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc {
- return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
+ return func(r *http.Request) (*http.Response, error) {
gauge.Inc()
defer gauge.Dec()
return next.RoundTrip(r)
- })
+ }
}
// InstrumentRoundTripperCounter is a middleware that wraps the provided
@@ -59,22 +59,28 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp
// If the wrapped RoundTripper panics or returns a non-nil error, the Counter
// is not incremented.
//
+// Use with WithExemplarFromContext to instrument the exemplars on the counter of requests.
+//
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
- rtOpts := &option{}
+ rtOpts := defaultOptions()
for _, o := range opts {
- o(rtOpts)
+ o.apply(rtOpts)
}
code, method := checkLabels(counter)
- return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
+ return func(r *http.Request) (*http.Response, error) {
resp, err := next.RoundTrip(r)
if err == nil {
- counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Inc()
+ addWithExemplar(
+ counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
+ 1,
+ rtOpts.getExemplarFn(r.Context()),
+ )
}
return resp, err
- })
+ }
}
// InstrumentRoundTripperDuration is a middleware that wraps the provided
@@ -94,24 +100,30 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
// If the wrapped RoundTripper panics or returns a non-nil error, no values are
// reported.
//
+// Use with WithExemplarFromContext to instrument the exemplars on the duration histograms.
+//
// Note that this method is only guaranteed to never observe negative durations
// if used with Go1.9+.
func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
- rtOpts := &option{}
+ rtOpts := defaultOptions()
for _, o := range opts {
- o(rtOpts)
+ o.apply(rtOpts)
}
code, method := checkLabels(obs)
- return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
+ return func(r *http.Request) (*http.Response, error) {
start := time.Now()
resp, err := next.RoundTrip(r)
if err == nil {
- obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Observe(time.Since(start).Seconds())
+ observeWithExemplar(
+ obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
+ time.Since(start).Seconds(),
+ rtOpts.getExemplarFn(r.Context()),
+ )
}
return resp, err
- })
+ }
}
// InstrumentTrace is used to offer flexibility in instrumenting the available
@@ -149,7 +161,7 @@ type InstrumentTrace struct {
//
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
- return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
+ return func(r *http.Request) (*http.Response, error) {
start := time.Now()
trace := &httptrace.ClientTrace{
@@ -231,5 +243,5 @@ func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) Ro
r = r.WithContext(httptrace.WithClientTrace(r.Context(), trace))
return next.RoundTrip(r)
- })
+ }
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
index a23f0edc6..cca67a78a 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
@@ -28,6 +28,26 @@ import (
// magicString is used for the hacky label test in checkLabels. Remove once fixed.
const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
+// observeWithExemplar is a wrapper for [prometheus.ExemplarAdder.ExemplarObserver],
+// which falls back to [prometheus.Observer.Observe] if no labels are provided.
+func observeWithExemplar(obs prometheus.Observer, val float64, labels map[string]string) {
+ if labels == nil {
+ obs.Observe(val)
+ return
+ }
+ obs.(prometheus.ExemplarObserver).ObserveWithExemplar(val, labels)
+}
+
+// addWithExemplar is a wrapper for [prometheus.ExemplarAdder.AddWithExemplar],
+// which falls back to [prometheus.Counter.Add] if no labels are provided.
+func addWithExemplar(obs prometheus.Counter, val float64, labels map[string]string) {
+ if labels == nil {
+ obs.Add(val)
+ return
+ }
+ obs.(prometheus.ExemplarAdder).AddWithExemplar(val, labels)
+}
+
// InstrumentHandlerInFlight is a middleware that wraps the provided
// http.Handler. It sets the provided prometheus.Gauge to the number of
// requests currently handled by the wrapped http.Handler.
@@ -48,7 +68,7 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl
// names are "code" and "method". The function panics otherwise. For the "method"
// label a predefined default label value set is used to filter given values.
// Values besides predefined values will count as `unknown` method.
-//`WithExtraMethods` can be used to add more methods to the set. The Observe
+// `WithExtraMethods` can be used to add more methods to the set. The Observe
// method of the Observer in the ObserverVec is called with the request duration
// in seconds. Partitioning happens by HTTP status code and/or HTTP method if
// the respective instance label names are present in the ObserverVec. For
@@ -62,28 +82,37 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl
// Note that this method is only guaranteed to never observe negative durations
// if used with Go1.9+.
func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
- mwOpts := &option{}
+ hOpts := defaultOptions()
for _, o := range opts {
- o(mwOpts)
+ o.apply(hOpts)
}
code, method := checkLabels(obs)
if code {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
now := time.Now()
d := newDelegator(w, nil)
next.ServeHTTP(d, r)
- obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
- })
+ observeWithExemplar(
+ obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
+ time.Since(now).Seconds(),
+ hOpts.getExemplarFn(r.Context()),
+ )
+ }
}
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
now := time.Now()
next.ServeHTTP(w, r)
- obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
- })
+
+ observeWithExemplar(
+ obs.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
+ time.Since(now).Seconds(),
+ hOpts.getExemplarFn(r.Context()),
+ )
+ }
}
// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
@@ -104,25 +133,34 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op
//
// See the example for InstrumentHandlerDuration for example usage.
func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, opts ...Option) http.HandlerFunc {
- mwOpts := &option{}
+ hOpts := defaultOptions()
for _, o := range opts {
- o(mwOpts)
+ o.apply(hOpts)
}
code, method := checkLabels(counter)
if code {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
d := newDelegator(w, nil)
next.ServeHTTP(d, r)
- counter.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Inc()
- })
+
+ addWithExemplar(
+ counter.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
+ 1,
+ hOpts.getExemplarFn(r.Context()),
+ )
+ }
}
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
- counter.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Inc()
- })
+ addWithExemplar(
+ counter.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
+ 1,
+ hOpts.getExemplarFn(r.Context()),
+ )
+ }
}
// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
@@ -148,20 +186,24 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler,
//
// See the example for InstrumentHandlerDuration for example usage.
func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
- mwOpts := &option{}
+ hOpts := defaultOptions()
for _, o := range opts {
- o(mwOpts)
+ o.apply(hOpts)
}
code, method := checkLabels(obs)
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
now := time.Now()
d := newDelegator(w, func(status int) {
- obs.With(labels(code, method, r.Method, status, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
+ observeWithExemplar(
+ obs.With(labels(code, method, r.Method, status, hOpts.extraMethods...)),
+ time.Since(now).Seconds(),
+ hOpts.getExemplarFn(r.Context()),
+ )
})
next.ServeHTTP(d, r)
- })
+ }
}
// InstrumentHandlerRequestSize is a middleware that wraps the provided
@@ -184,27 +226,34 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
//
// See the example for InstrumentHandlerDuration for example usage.
func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
- mwOpts := &option{}
+ hOpts := defaultOptions()
for _, o := range opts {
- o(mwOpts)
+ o.apply(hOpts)
}
code, method := checkLabels(obs)
-
if code {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
d := newDelegator(w, nil)
next.ServeHTTP(d, r)
size := computeApproximateRequestSize(r)
- obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(size))
- })
+ observeWithExemplar(
+ obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
+ float64(size),
+ hOpts.getExemplarFn(r.Context()),
+ )
+ }
}
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
size := computeApproximateRequestSize(r)
- obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(float64(size))
- })
+ observeWithExemplar(
+ obs.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
+ float64(size),
+ hOpts.getExemplarFn(r.Context()),
+ )
+ }
}
// InstrumentHandlerResponseSize is a middleware that wraps the provided
@@ -227,9 +276,9 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler,
//
// See the example for InstrumentHandlerDuration for example usage.
func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.Handler {
- mwOpts := &option{}
+ hOpts := defaultOptions()
for _, o := range opts {
- o(mwOpts)
+ o.apply(hOpts)
}
code, method := checkLabels(obs)
@@ -237,7 +286,11 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
d := newDelegator(w, nil)
next.ServeHTTP(d, r)
- obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(d.Written()))
+ observeWithExemplar(
+ obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
+ float64(d.Written()),
+ hOpts.getExemplarFn(r.Context()),
+ )
})
}
@@ -246,7 +299,7 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
// Collector does not have a Desc or has more than one Desc or its Desc is
// invalid. It also panics if the Collector has any non-const, non-curried
// labels that are not named "code" or "method".
-func checkLabels(c prometheus.Collector) (code bool, method bool) {
+func checkLabels(c prometheus.Collector) (code, method bool) {
// TODO(beorn7): Remove this hacky way to check for instance labels
// once Descriptors can have their dimensionality queried.
var (
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/option.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/option.go
index 35e41bd1e..c590d912c 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/option.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/option.go
@@ -13,19 +13,46 @@
package promhttp
-// Option are used to configure a middleware or round tripper..
-type Option func(*option)
+import (
+ "context"
-type option struct {
- extraMethods []string
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+// Option are used to configure both handler (middleware) or round tripper.
+type Option interface {
+ apply(*options)
+}
+
+// options store options for both a handler or round tripper.
+type options struct {
+ extraMethods []string
+ getExemplarFn func(requestCtx context.Context) prometheus.Labels
+}
+
+func defaultOptions() *options {
+ return &options{getExemplarFn: func(ctx context.Context) prometheus.Labels { return nil }}
}
+type optionApplyFunc func(*options)
+
+func (o optionApplyFunc) apply(opt *options) { o(opt) }
+
// WithExtraMethods adds additional HTTP methods to the list of allowed methods.
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for the default list.
//
// See the example for ExampleInstrumentHandlerWithExtraMethods for example usage.
func WithExtraMethods(methods ...string) Option {
- return func(o *option) {
+ return optionApplyFunc(func(o *options) {
o.extraMethods = methods
- }
+ })
+}
+
+// WithExemplarFromContext adds allows to put a hook to all counter and histogram metrics.
+// If the hook function returns non-nil labels, exemplars will be added for that request, otherwise metric
+// will get instrumented without exemplar.
+func WithExemplarFromContext(getExemplarFn func(requestCtx context.Context) prometheus.Labels) Option {
+ return optionApplyFunc(func(o *options) {
+ o.getExemplarFn = getExemplarFn
+ })
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/registry.go b/vendor/github.com/prometheus/client_golang/prometheus/registry.go
index 383a7f594..09e34d307 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/registry.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/registry.go
@@ -15,8 +15,8 @@ package prometheus
import (
"bytes"
+ "errors"
"fmt"
- "io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -252,9 +252,12 @@ func (errs MultiError) MaybeUnwrap() error {
}
// Registry registers Prometheus collectors, collects their metrics, and gathers
-// them into MetricFamilies for exposition. It implements both Registerer and
-// Gatherer. The zero value is not usable. Create instances with NewRegistry or
-// NewPedanticRegistry.
+// them into MetricFamilies for exposition. It implements Registerer, Gatherer,
+// and Collector. The zero value is not usable. Create instances with
+// NewRegistry or NewPedanticRegistry.
+//
+// Registry implements Collector to allow it to be used for creating groups of
+// metrics. See the Grouping example for how this can be done.
type Registry struct {
mtx sync.RWMutex
collectorsByID map[uint64]Collector // ID is a hash of the descIDs.
@@ -289,7 +292,7 @@ func (r *Registry) Register(c Collector) error {
// Is the descriptor valid at all?
if desc.err != nil {
- return fmt.Errorf("descriptor %s is invalid: %s", desc, desc.err)
+ return fmt.Errorf("descriptor %s is invalid: %w", desc, desc.err)
}
// Is the descID unique?
@@ -407,6 +410,14 @@ func (r *Registry) MustRegister(cs ...Collector) {
// Gather implements Gatherer.
func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
+ r.mtx.RLock()
+
+ if len(r.collectorsByID) == 0 && len(r.uncheckedCollectors) == 0 {
+ // Fast path.
+ r.mtx.RUnlock()
+ return nil, nil
+ }
+
var (
checkedMetricChan = make(chan Metric, capMetricChan)
uncheckedMetricChan = make(chan Metric, capMetricChan)
@@ -416,7 +427,6 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
)
- r.mtx.RLock()
goroutineBudget := len(r.collectorsByID) + len(r.uncheckedCollectors)
metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
checkedCollectors := make(chan Collector, len(r.collectorsByID))
@@ -549,6 +559,31 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
}
+// Describe implements Collector.
+func (r *Registry) Describe(ch chan<- *Desc) {
+ r.mtx.RLock()
+ defer r.mtx.RUnlock()
+
+ // Only report the checked Collectors; unchecked collectors don't report any
+ // Desc.
+ for _, c := range r.collectorsByID {
+ c.Describe(ch)
+ }
+}
+
+// Collect implements Collector.
+func (r *Registry) Collect(ch chan<- Metric) {
+ r.mtx.RLock()
+ defer r.mtx.RUnlock()
+
+ for _, c := range r.collectorsByID {
+ c.Collect(ch)
+ }
+ for _, c := range r.uncheckedCollectors {
+ c.Collect(ch)
+ }
+}
+
// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the
// Prometheus text format, and writes it to a temporary file. Upon success, the
// temporary file is renamed to the provided filename.
@@ -556,7 +591,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
// This is intended for use with the textfile collector of the node exporter.
// Note that the node exporter expects the filename to be suffixed with ".prom".
func WriteToTextfile(filename string, g Gatherer) error {
- tmp, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
+ tmp, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename))
if err != nil {
return err
}
@@ -575,7 +610,7 @@ func WriteToTextfile(filename string, g Gatherer) error {
return err
}
- if err := os.Chmod(tmp.Name(), 0644); err != nil {
+ if err := os.Chmod(tmp.Name(), 0o644); err != nil {
return err
}
return os.Rename(tmp.Name(), filename)
@@ -596,7 +631,7 @@ func processMetric(
}
dtoMetric := &dto.Metric{}
if err := metric.Write(dtoMetric); err != nil {
- return fmt.Errorf("error collecting metric %v: %s", desc, err)
+ return fmt.Errorf("error collecting metric %v: %w", desc, err)
}
metricFamily, ok := metricFamiliesByName[desc.fqName]
if ok { // Existing name.
@@ -718,12 +753,13 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
for i, g := range gs {
mfs, err := g.Gather()
if err != nil {
- if multiErr, ok := err.(MultiError); ok {
+ multiErr := MultiError{}
+ if errors.As(err, &multiErr) {
for _, err := range multiErr {
- errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
+ errs = append(errs, fmt.Errorf("[from Gatherer #%d] %w", i+1, err))
}
} else {
- errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
+ errs = append(errs, fmt.Errorf("[from Gatherer #%d] %w", i+1, err))
}
}
for _, mf := range mfs {
@@ -884,11 +920,11 @@ func checkMetricConsistency(
h.Write(separatorByteSlice)
// Make sure label pairs are sorted. We depend on it for the consistency
// check.
- if !sort.IsSorted(labelPairSorter(dtoMetric.Label)) {
+ if !sort.IsSorted(internal.LabelPairSorter(dtoMetric.Label)) {
// We cannot sort dtoMetric.Label in place as it is immutable by contract.
copiedLabels := make([]*dto.LabelPair, len(dtoMetric.Label))
copy(copiedLabels, dtoMetric.Label)
- sort.Sort(labelPairSorter(copiedLabels))
+ sort.Sort(internal.LabelPairSorter(copiedLabels))
dtoMetric.Label = copiedLabels
}
for _, lp := range dtoMetric.Label {
@@ -935,7 +971,7 @@ func checkDescConsistency(
metricFamily.GetName(), dtoMetric, desc,
)
}
- sort.Sort(labelPairSorter(lpsFromDesc))
+ sort.Sort(internal.LabelPairSorter(lpsFromDesc))
for i, lpFromDesc := range lpsFromDesc {
lpFromMetric := dtoMetric.Label[i]
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
@@ -948,3 +984,89 @@ func checkDescConsistency(
}
return nil
}
+
+var _ TransactionalGatherer = &MultiTRegistry{}
+
+// MultiTRegistry is a TransactionalGatherer that joins gathered metrics from multiple
+// transactional gatherers.
+//
+// It is caller responsibility to ensure two registries have mutually exclusive metric families,
+// no deduplication will happen.
+type MultiTRegistry struct {
+ tGatherers []TransactionalGatherer
+}
+
+// NewMultiTRegistry creates MultiTRegistry.
+func NewMultiTRegistry(tGatherers ...TransactionalGatherer) *MultiTRegistry {
+ return &MultiTRegistry{
+ tGatherers: tGatherers,
+ }
+}
+
+// Gather implements TransactionalGatherer interface.
+func (r *MultiTRegistry) Gather() (mfs []*dto.MetricFamily, done func(), err error) {
+ errs := MultiError{}
+
+ dFns := make([]func(), 0, len(r.tGatherers))
+ // TODO(bwplotka): Implement concurrency for those?
+ for _, g := range r.tGatherers {
+ // TODO(bwplotka): Check for duplicates?
+ m, d, err := g.Gather()
+ errs.Append(err)
+
+ mfs = append(mfs, m...)
+ dFns = append(dFns, d)
+ }
+
+ // TODO(bwplotka): Consider sort in place, given metric family in gather is sorted already.
+ sort.Slice(mfs, func(i, j int) bool {
+ return *mfs[i].Name < *mfs[j].Name
+ })
+ return mfs, func() {
+ for _, d := range dFns {
+ d()
+ }
+ }, errs.MaybeUnwrap()
+}
+
+// TransactionalGatherer represents transactional gatherer that can be triggered to notify gatherer that memory
+// used by metric family is no longer used by a caller. This allows implementations with cache.
+type TransactionalGatherer interface {
+ // Gather returns metrics in a lexicographically sorted slice
+ // of uniquely named MetricFamily protobufs. Gather ensures that the
+ // returned slice is valid and self-consistent so that it can be used
+ // for valid exposition. As an exception to the strict consistency
+ // requirements described for metric.Desc, Gather will tolerate
+ // different sets of label names for metrics of the same metric family.
+ //
+ // Even if an error occurs, Gather attempts to gather as many metrics as
+ // possible. Hence, if a non-nil error is returned, the returned
+ // MetricFamily slice could be nil (in case of a fatal error that
+ // prevented any meaningful metric collection) or contain a number of
+ // MetricFamily protobufs, some of which might be incomplete, and some
+ // might be missing altogether. The returned error (which might be a
+ // MultiError) explains the details. Note that this is mostly useful for
+ // debugging purposes. If the gathered protobufs are to be used for
+ // exposition in actual monitoring, it is almost always better to not
+ // expose an incomplete result and instead disregard the returned
+ // MetricFamily protobufs in case the returned error is non-nil.
+ //
+ // Important: done is expected to be triggered (even if the error occurs!)
+ // once caller does not need returned slice of dto.MetricFamily.
+ Gather() (_ []*dto.MetricFamily, done func(), err error)
+}
+
+// ToTransactionalGatherer transforms Gatherer to transactional one with noop as done function.
+func ToTransactionalGatherer(g Gatherer) TransactionalGatherer {
+ return &noTransactionGatherer{g: g}
+}
+
+type noTransactionGatherer struct {
+ g Gatherer
+}
+
+// Gather implements TransactionalGatherer interface.
+func (g *noTransactionGatherer) Gather() (_ []*dto.MetricFamily, done func(), err error) {
+ mfs, err := g.g.Gather()
+ return mfs, func() {}, err
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/summary.go b/vendor/github.com/prometheus/client_golang/prometheus/summary.go
index c5fa8ed7c..7bc448a89 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/summary.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/summary.go
@@ -603,7 +603,8 @@ func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
// WithLabelValues works as GetMetricWithLabelValues, but panics where
// GetMetricWithLabelValues would have returned an error. Not returning an
// error allows shortcuts like
-// myVec.WithLabelValues("404", "GET").Observe(42.21)
+//
+// myVec.WithLabelValues("404", "GET").Observe(42.21)
func (v *SummaryVec) WithLabelValues(lvs ...string) Observer {
s, err := v.GetMetricWithLabelValues(lvs...)
if err != nil {
@@ -614,7 +615,8 @@ func (v *SummaryVec) WithLabelValues(lvs ...string) Observer {
// 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)
+//
+// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
func (v *SummaryVec) With(labels Labels) Observer {
s, err := v.GetMetricWith(labels)
if err != nil {
@@ -701,7 +703,8 @@ func (s *constSummary) Write(out *dto.Metric) error {
//
// quantiles maps ranks to quantile values. For example, a median latency of
// 0.23s and a 99th percentile latency of 0.56s would be expressed as:
-// map[float64]float64{0.5: 0.23, 0.99: 0.56}
+//
+// map[float64]float64{0.5: 0.23, 0.99: 0.56}
//
// NewConstSummary returns an error if the length of labelValues is not
// consistent with the variable labels in Desc or if Desc is invalid.
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/testutil/promlint/promlint.go b/vendor/github.com/prometheus/client_golang/prometheus/testutil/promlint/promlint.go
index ec8061706..a20f159b7 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/testutil/promlint/promlint.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/testutil/promlint/promlint.go
@@ -15,6 +15,7 @@
package promlint
import (
+ "errors"
"fmt"
"io"
"regexp"
@@ -83,7 +84,7 @@ func (l *Linter) Lint() ([]Problem, error) {
mf := &dto.MetricFamily{}
for {
if err := d.Decode(mf); err != nil {
- if err == io.EOF {
+ if errors.Is(err, io.EOF) {
break
}
@@ -283,7 +284,7 @@ func lintUnitAbbreviations(mf *dto.MetricFamily) []Problem {
// metricUnits attempts to detect known unit types used as part of a metric name,
// e.g. "foo_bytes_total" or "bar_baz_milligrams".
-func metricUnits(m string) (unit string, base string, ok bool) {
+func metricUnits(m string) (unit, base string, ok bool) {
ss := strings.Split(m, "_")
for unit, base := range units {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/timer.go b/vendor/github.com/prometheus/client_golang/prometheus/timer.go
index 8d5f10523..f28a76f3a 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/timer.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/timer.go
@@ -25,11 +25,12 @@ type Timer struct {
// NewTimer creates a new Timer. The provided Observer is used to observe a
// duration in seconds. Timer is usually used to time a function call in the
// following way:
-// func TimeMe() {
-// timer := NewTimer(myHistogram)
-// defer timer.ObserveDuration()
-// // Do actual work.
-// }
+//
+// func TimeMe() {
+// timer := NewTimer(myHistogram)
+// defer timer.ObserveDuration()
+// // Do actual work.
+// }
func NewTimer(o Observer) *Timer {
return &Timer{
begin: time.Now(),
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/value.go b/vendor/github.com/prometheus/client_golang/prometheus/value.go
index b4e0ae11c..2d3abc1cb 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/value.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/value.go
@@ -23,6 +23,8 @@ import (
"github.com/golang/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
+ "github.com/prometheus/client_golang/prometheus/internal"
+
dto "github.com/prometheus/client_model/go"
)
@@ -38,6 +40,23 @@ const (
UntypedValue
)
+var (
+ CounterMetricTypePtr = func() *dto.MetricType { d := dto.MetricType_COUNTER; return &d }()
+ GaugeMetricTypePtr = func() *dto.MetricType { d := dto.MetricType_GAUGE; return &d }()
+ UntypedMetricTypePtr = func() *dto.MetricType { d := dto.MetricType_UNTYPED; return &d }()
+)
+
+func (v ValueType) ToDTO() *dto.MetricType {
+ switch v {
+ case CounterValue:
+ return CounterMetricTypePtr
+ case GaugeValue:
+ return GaugeMetricTypePtr
+ default:
+ return UntypedMetricTypePtr
+ }
+}
+
// valueFunc is a generic metric for simple values retrieved on collect time
// from a function. It implements Metric and Collector. Its effective type is
// determined by ValueType. This is a low-level building block used by the
@@ -91,11 +110,15 @@ func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
return nil, err
}
+
+ metric := &dto.Metric{}
+ if err := populateMetric(valueType, value, MakeLabelPairs(desc, labelValues), nil, metric); err != nil {
+ return nil, err
+ }
+
return &constMetric{
- desc: desc,
- valType: valueType,
- val: value,
- labelPairs: MakeLabelPairs(desc, labelValues),
+ desc: desc,
+ metric: metric,
}, nil
}
@@ -110,10 +133,8 @@ func MustNewConstMetric(desc *Desc, valueType ValueType, value float64, labelVal
}
type constMetric struct {
- desc *Desc
- valType ValueType
- val float64
- labelPairs []*dto.LabelPair
+ desc *Desc
+ metric *dto.Metric
}
func (m *constMetric) Desc() *Desc {
@@ -121,7 +142,11 @@ func (m *constMetric) Desc() *Desc {
}
func (m *constMetric) Write(out *dto.Metric) error {
- return populateMetric(m.valType, m.val, m.labelPairs, nil, out)
+ out.Label = m.metric.Label
+ out.Counter = m.metric.Counter
+ out.Gauge = m.metric.Gauge
+ out.Untyped = m.metric.Untyped
+ return nil
}
func populateMetric(
@@ -170,12 +195,12 @@ func MakeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
})
}
labelPairs = append(labelPairs, desc.constLabelPairs...)
- sort.Sort(labelPairSorter(labelPairs))
+ sort.Sort(internal.LabelPairSorter(labelPairs))
return labelPairs
}
// ExemplarMaxRunes is the max total number of runes allowed in exemplar labels.
-const ExemplarMaxRunes = 64
+const ExemplarMaxRunes = 128
// newExemplar creates a new dto.Exemplar from the provided values. An error is
// returned if any of the label names or values are invalid or if the total
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/vec.go b/vendor/github.com/prometheus/client_golang/prometheus/vec.go
index 4ababe6c9..7ae322590 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/vec.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/vec.go
@@ -99,6 +99,16 @@ func (m *MetricVec) Delete(labels Labels) bool {
return m.metricMap.deleteByHashWithLabels(h, labels, m.curry)
}
+// DeletePartialMatch deletes all metrics where the variable labels contain all of those
+// passed in as labels. The order of the labels does not matter.
+// It returns the number of metrics deleted.
+//
+// Note that curried labels will never be matched if deleting from the curried vector.
+// To match curried labels with DeletePartialMatch, it must be called on the base vector.
+func (m *MetricVec) DeletePartialMatch(labels Labels) int {
+ return m.metricMap.deleteByLabels(labels, m.curry)
+}
+
// Without explicit forwarding of Describe, Collect, Reset, those methods won't
// show up in GoDoc.
@@ -381,6 +391,82 @@ func (m *metricMap) deleteByHashWithLabels(
return true
}
+// deleteByLabels deletes a metric if the given labels are present in the metric.
+func (m *metricMap) deleteByLabels(labels Labels, curry []curriedLabelValue) int {
+ m.mtx.Lock()
+ defer m.mtx.Unlock()
+
+ var numDeleted int
+
+ for h, metrics := range m.metrics {
+ i := findMetricWithPartialLabels(m.desc, metrics, labels, curry)
+ if i >= len(metrics) {
+ // Didn't find matching labels in this metric slice.
+ continue
+ }
+ delete(m.metrics, h)
+ numDeleted++
+ }
+
+ return numDeleted
+}
+
+// findMetricWithPartialLabel returns the index of the matching metric or
+// len(metrics) if not found.
+func findMetricWithPartialLabels(
+ desc *Desc, metrics []metricWithLabelValues, labels Labels, curry []curriedLabelValue,
+) int {
+ for i, metric := range metrics {
+ if matchPartialLabels(desc, metric.values, labels, curry) {
+ return i
+ }
+ }
+ return len(metrics)
+}
+
+// indexOf searches the given slice of strings for the target string and returns
+// the index or len(items) as well as a boolean whether the search succeeded.
+func indexOf(target string, items []string) (int, bool) {
+ for i, l := range items {
+ if l == target {
+ return i, true
+ }
+ }
+ return len(items), false
+}
+
+// valueMatchesVariableOrCurriedValue determines if a value was previously curried,
+// and returns whether it matches either the "base" value or the curried value accordingly.
+// It also indicates whether the match is against a curried or uncurried value.
+func valueMatchesVariableOrCurriedValue(targetValue string, index int, values []string, curry []curriedLabelValue) (bool, bool) {
+ for _, curriedValue := range curry {
+ if curriedValue.index == index {
+ // This label was curried. See if the curried value matches our target.
+ return curriedValue.value == targetValue, true
+ }
+ }
+ // This label was not curried. See if the current value matches our target label.
+ return values[index] == targetValue, false
+}
+
+// matchPartialLabels searches the current metric and returns whether all of the target label:value pairs are present.
+func matchPartialLabels(desc *Desc, values []string, labels Labels, curry []curriedLabelValue) bool {
+ for l, v := range labels {
+ // Check if the target label exists in our metrics and get the index.
+ varLabelIndex, validLabel := indexOf(l, desc.variableLabels)
+ if validLabel {
+ // Check the value of that label against the target value.
+ // We don't consider curried values in partial matches.
+ matches, curried := valueMatchesVariableOrCurriedValue(v, varLabelIndex, values, curry)
+ if matches && !curried {
+ continue
+ }
+ }
+ return false
+ }
+ return true
+}
+
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
// or creates it and returns the new one.
//
@@ -485,7 +571,7 @@ func findMetricWithLabels(
return len(metrics)
}
-func matchLabelValues(values []string, lvs []string, curry []curriedLabelValue) bool {
+func matchLabelValues(values, lvs []string, curry []curriedLabelValue) bool {
if len(values) != len(lvs)+len(curry) {
return false
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/wrap.go b/vendor/github.com/prometheus/client_golang/prometheus/wrap.go
index 74ee93280..1498ee144 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/wrap.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/wrap.go
@@ -21,6 +21,8 @@ import (
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
+
+ "github.com/prometheus/client_golang/prometheus/internal"
)
// WrapRegistererWith returns a Registerer wrapping the provided
@@ -182,7 +184,7 @@ func (m *wrappingMetric) Write(out *dto.Metric) error {
Value: proto.String(lv),
})
}
- sort.Sort(labelPairSorter(out.Label))
+ sort.Sort(internal.LabelPairSorter(out.Label))
return nil
}