From 461fdac0986f1f16480ed1d93b582d5ce6a370da Mon Sep 17 00:00:00 2001 From: moralpriest Date: Wed, 15 Jul 2026 11:43:50 -0600 Subject: [PATCH] chore(deps): bump VictoriaMetrics/metrics to v1.38.0 (+ histogram, fastrand) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vendored dependency bumps (prep for eventual Go toolchain bump). - github.com/VictoriaMetrics/metrics: v1.38.0 (go 1.17, latest go<=1.17) - github.com/valyala/histogram: v1.2.0 (go 1.12) — required by metrics - github.com/valyala/fastrand: v1.1.0 (no go directive; old format) — TRANSITIVE: metrics v1.38.0 histogram.go uses fastrand.RNG.Seed, which only exists in fastrand v1.1.0. Note: metrics v1.38.0 go.mod also lists golang.org/x/sys v0.15.0, but that is only imported by process_metrics_windows.go (build-tagged windows-only), so it is not needed for the Linux build. Verification: - go build ./... green (after adding fastrand v1.1.0 for the Seed API). - go test ./...: metrics own suite + importers pass. Only pre-existing failure is Test_Payload_TX (walletapi, reproduced on clean community-dev). - derod --testnet starts, logs cleanly (no errors/panics). --- .../metrics/.github/workflows/main.yml | 11 +- .../VictoriaMetrics/metrics/README.md | 26 +- .../VictoriaMetrics/metrics/counter.go | 21 +- .../VictoriaMetrics/metrics/floatcounter.go | 16 +- .../VictoriaMetrics/metrics/gauge.go | 79 ++- .../VictoriaMetrics/metrics/gauge_test.go | 77 ++- .../github.com/VictoriaMetrics/metrics/go.mod | 9 +- .../github.com/VictoriaMetrics/metrics/go.sum | 10 +- .../VictoriaMetrics/metrics/go_metrics.go | 194 +++++-- .../metrics/go_metrics_test.go | 71 +++ .../VictoriaMetrics/metrics/histogram.go | 68 ++- .../metrics/histogram_example_test.go | 2 +- .../VictoriaMetrics/metrics/histogram_test.go | 43 ++ .../VictoriaMetrics/metrics/metrics.go | 339 ++++++++++-- .../VictoriaMetrics/metrics/metrics_test.go | 150 ++++++ .../metrics/process_metrics_linux.go | 209 +++++-- .../metrics/process_metrics_other.go | 3 +- .../metrics/process_metrics_windows.go | 84 +++ .../metrics/prometheus_histogram.go | 273 ++++++++++ .../prometheus_histogram_example_test.go | 27 + .../metrics/prometheus_histogram_test.go | 194 +++++++ .../VictoriaMetrics/metrics/push.go | 510 ++++++++++++++++++ .../VictoriaMetrics/metrics/push_test.go | 243 +++++++++ .../metrics/push_timing_test.go | 20 + .../github.com/VictoriaMetrics/metrics/set.go | 281 +++++++--- .../metrics/set_example_test.go | 53 ++ .../VictoriaMetrics/metrics/set_test.go | 21 + .../VictoriaMetrics/metrics/summary.go | 42 +- .../VictoriaMetrics/metrics/summary_test.go | 14 + .../VictoriaMetrics/metrics/validator.go | 9 +- .../VictoriaMetrics/metrics/validator_test.go | 4 +- .../github.com/valyala/fastrand/.travis.yml | 16 - .../github.com/valyala/fastrand/LICENSE | 21 - .../github.com/valyala/fastrand/README.md | 76 --- .../github.com/valyala/fastrand/fastrand.go | 74 --- .../vendor/github.com/valyala/fastrand/go.mod | 1 - .../github.com/valyala/histogram/LICENSE | 21 - .../github.com/valyala/histogram/README.md | 9 - .../github.com/valyala/histogram/go.mod | 5 - .../github.com/valyala/histogram/go.sum | 2 - .../github.com/valyala/histogram/histogram.go | 127 ----- .../metrics/vendor/modules.txt | 9 +- .../github.com/valyala/fastrand/fastrand.go | 5 + .../valyala/fastrand/fastrand_test.go | 90 ++++ .../valyala/fastrand/fastrand_timing_test.go | 130 +++++ vendor/github.com/valyala/histogram/go.mod | 2 +- vendor/github.com/valyala/histogram/go.sum | 4 +- .../github.com/valyala/histogram/histogram.go | 4 + .../valyala/histogram/histogram_test.go | 21 + .../github.com/valyala/fastrand/.travis.yml | 16 - .../github.com/valyala/fastrand/LICENSE | 21 - .../github.com/valyala/fastrand/README.md | 76 --- .../github.com/valyala/fastrand/fastrand.go | 74 --- .../vendor/github.com/valyala/fastrand/go.mod | 1 - .../valyala/histogram/vendor/modules.txt | 2 +- 55 files changed, 3106 insertions(+), 804 deletions(-) create mode 100644 vendor/github.com/VictoriaMetrics/metrics/go_metrics_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/process_metrics_windows.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram_example_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/push.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/push_test.go create mode 100644 vendor/github.com/VictoriaMetrics/metrics/push_timing_test.go delete mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/.travis.yml delete mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/LICENSE delete mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/README.md delete mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/fastrand.go delete mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/go.mod delete mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/LICENSE delete mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/README.md delete mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.mod delete mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.sum delete mode 100644 vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/histogram.go create mode 100644 vendor/github.com/valyala/fastrand/fastrand_test.go create mode 100644 vendor/github.com/valyala/fastrand/fastrand_timing_test.go delete mode 100644 vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/.travis.yml delete mode 100644 vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/LICENSE delete mode 100644 vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/README.md delete mode 100644 vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/fastrand.go delete mode 100644 vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/go.mod diff --git a/vendor/github.com/VictoriaMetrics/metrics/.github/workflows/main.yml b/vendor/github.com/VictoriaMetrics/metrics/.github/workflows/main.yml index d8d8d12b..88d0a374 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/.github/workflows/main.yml +++ b/vendor/github.com/VictoriaMetrics/metrics/.github/workflows/main.yml @@ -8,15 +8,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v5 with: - go-version: 1.13 + go-version: stable id: go - name: Code checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Test run: | go test -v ./... -coverprofile=coverage.txt -covermode=atomic + GOARCH=386 go test ./... -coverprofile=coverage.txt -covermode=atomic go test -v ./... -race - name: Build run: | @@ -26,8 +27,8 @@ jobs: GOOS=windows go build GOARCH=386 go build - name: Publish coverage - uses: codecov/codecov-action@v1.0.6 + uses: codecov/codecov-action@v5 with: token: ${{secrets.CODECOV_TOKEN}} - file: ./coverage.txt + files: ./coverage.txt diff --git a/vendor/github.com/VictoriaMetrics/metrics/README.md b/vendor/github.com/VictoriaMetrics/metrics/README.md index 5eef96a6..4984dd93 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/README.md +++ b/vendor/github.com/VictoriaMetrics/metrics/README.md @@ -16,6 +16,9 @@ * Allows exporting distinct metric sets via distinct endpoints. See [Set](http://godoc.org/github.com/VictoriaMetrics/metrics#Set). * Supports [easy-to-use histograms](http://godoc.org/github.com/VictoriaMetrics/metrics#Histogram), which just work without any tuning. Read more about VictoriaMetrics histograms at [this article](https://medium.com/@valyala/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350). +* Can push metrics to VictoriaMetrics or to any other remote storage, which accepts metrics + in [Prometheus text exposition format](https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format). + See [these docs](http://godoc.org/github.com/VictoriaMetrics/metrics#InitPush). ### Limitations @@ -28,8 +31,8 @@ ```go import "github.com/VictoriaMetrics/metrics" -// Register various time series. -// Time series name may contain labels in Prometheus format - see below. +// Register various metrics. +// Metric name may contain labels in Prometheus format - see below. var ( // Register counter without labels. requestsTotal = metrics.NewCounter("requests_total") @@ -64,10 +67,17 @@ func requestHandler() { http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { metrics.WritePrometheus(w, true) }) + +// ... or push registered metrics every 10 seconds to http://victoria-metrics:8428/api/v1/import/prometheus +// with the added `instance="foobar"` label to all the pushed metrics. +metrics.InitPush("http://victoria-metrics:8428/api/v1/import/prometheus", 10*time.Second, `instance="foobar"`, true) ``` -See [docs](http://godoc.org/github.com/VictoriaMetrics/metrics) for more info. +By default, exposed metrics [do not have](https://github.com/VictoriaMetrics/metrics/issues/48#issuecomment-1620765811) +`TYPE` or `HELP` meta information. Call [`ExposeMetadata(true)`](https://pkg.go.dev/github.com/VictoriaMetrics/metrics#ExposeMetadata) +in order to generate `TYPE` and `HELP` meta information per each metric. +See [docs](https://pkg.go.dev/github.com/VictoriaMetrics/metrics) for more info. ### Users @@ -86,8 +96,8 @@ Because the `github.com/prometheus/client_golang` is too complex and is hard to #### Why the `metrics.WritePrometheus` doesn't expose documentation for each metric? Because this documentation is ignored by Prometheus. The documentation is for users. -Just give meaningful names to the exported metrics or add comments in the source code -or in other suitable place explaining each metric exposed from your application. +Just give [meaningful names to the exported metrics](https://prometheus.io/docs/practices/naming/#metric-names) +or add comments in the source code or in other suitable place explaining each metric exposed from your application. #### How to implement [CounterVec](https://godoc.org/github.com/prometheus/client_golang/prometheus#CounterVec) in `metrics`? @@ -98,7 +108,9 @@ instead of `CounterVec.With`. See [this example](https://pkg.go.dev/github.com/V #### Why [Histogram](http://godoc.org/github.com/VictoriaMetrics/metrics#Histogram) buckets contain `vmrange` labels instead of `le` labels like in Prometheus histograms? -Buckets with `vmrange` labels occupy less disk space compared to Promethes-style buckets with `le` labels, +Buckets with `vmrange` labels occupy less disk space compared to Prometheus-style buckets with `le` labels, because `vmrange` buckets don't include counters for the previous ranges. [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) provides `prometheus_buckets` function, which converts `vmrange` buckets to Prometheus-style buckets with `le` labels. This is useful for building heatmaps in Grafana. -Additionally, its' `histogram_quantile` function transparently handles histogram buckets with `vmrange` labels. +Additionally, its `histogram_quantile` function transparently handles histogram buckets with `vmrange` labels. + +However, for compatibility purposes package provides classic [Prometheus Histograms](http://godoc.org/github.com/VictoriaMetrics/metrics#PrometheusHistogram) with `le` labels. diff --git a/vendor/github.com/VictoriaMetrics/metrics/counter.go b/vendor/github.com/VictoriaMetrics/metrics/counter.go index a7d95492..1076e80c 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/counter.go +++ b/vendor/github.com/VictoriaMetrics/metrics/counter.go @@ -11,9 +11,9 @@ import ( // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned counter is safe to use from concurrent goroutines. func NewCounter(name string) *Counter { @@ -42,6 +42,11 @@ func (c *Counter) Add(n int) { atomic.AddUint64(&c.n, uint64(n)) } +// AddInt64 adds n to c. +func (c *Counter) AddInt64(n int64) { + atomic.AddUint64(&c.n, uint64(n)) +} + // Get returns the current value for c. func (c *Counter) Get() uint64 { return atomic.LoadUint64(&c.n) @@ -58,6 +63,10 @@ func (c *Counter) marshalTo(prefix string, w io.Writer) { fmt.Fprintf(w, "%s %d\n", prefix, v) } +func (c *Counter) metricType() string { + return "counter" +} + // GetOrCreateCounter returns registered counter with the given name // or creates new counter if the registry doesn't contain counter with // the given name. @@ -65,9 +74,9 @@ func (c *Counter) marshalTo(prefix string, w io.Writer) { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned counter is safe to use from concurrent goroutines. // diff --git a/vendor/github.com/VictoriaMetrics/metrics/floatcounter.go b/vendor/github.com/VictoriaMetrics/metrics/floatcounter.go index d01dd851..8bd9fa67 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/floatcounter.go +++ b/vendor/github.com/VictoriaMetrics/metrics/floatcounter.go @@ -11,9 +11,9 @@ import ( // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned counter is safe to use from concurrent goroutines. func NewFloatCounter(name string) *FloatCounter { @@ -63,6 +63,10 @@ func (fc *FloatCounter) marshalTo(prefix string, w io.Writer) { fmt.Fprintf(w, "%s %g\n", prefix, v) } +func (fc *FloatCounter) metricType() string { + return "counter" +} + // GetOrCreateFloatCounter returns registered FloatCounter with the given name // or creates new FloatCounter if the registry doesn't contain FloatCounter with // the given name. @@ -70,9 +74,9 @@ func (fc *FloatCounter) marshalTo(prefix string, w io.Writer) { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned FloatCounter is safe to use from concurrent goroutines. // diff --git a/vendor/github.com/VictoriaMetrics/metrics/gauge.go b/vendor/github.com/VictoriaMetrics/metrics/gauge.go index 05bf1473..3573e144 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/gauge.go +++ b/vendor/github.com/VictoriaMetrics/metrics/gauge.go @@ -3,19 +3,21 @@ package metrics import ( "fmt" "io" + "math" + "sync/atomic" ) -// NewGauge registers and returns gauge with the given name, which calls f -// to obtain gauge value. +// NewGauge registers and returns gauge with the given name, which calls f to obtain gauge value. // // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // f must be safe for concurrent calls. +// if f is nil, then it is expected that the gauge value is changed via Set(), Inc(), Dec() and Add() calls. // // The returned gauge is safe to use from concurrent goroutines. // @@ -25,19 +27,68 @@ func NewGauge(name string, f func() float64) *Gauge { } // Gauge is a float64 gauge. -// -// See also Counter, which could be used as a gauge with Set and Dec calls. type Gauge struct { + // valueBits contains uint64 representation of float64 passed to Gauge.Set. + valueBits uint64 + + // f is a callback, which is called for returning the gauge value. f func() float64 } // Get returns the current value for g. func (g *Gauge) Get() float64 { - return g.f() + if f := g.f; f != nil { + return f() + } + n := atomic.LoadUint64(&g.valueBits) + return math.Float64frombits(n) +} + +// Set sets g value to v. +// +// The g must be created with nil callback in order to be able to call this function. +func (g *Gauge) Set(v float64) { + if g.f != nil { + panic(fmt.Errorf("cannot call Set on gauge created with non-nil callback")) + } + n := math.Float64bits(v) + atomic.StoreUint64(&g.valueBits, n) +} + +// Inc increments g by 1. +// +// The g must be created with nil callback in order to be able to call this function. +func (g *Gauge) Inc() { + g.Add(1) +} + +// Dec decrements g by 1. +// +// The g must be created with nil callback in order to be able to call this function. +func (g *Gauge) Dec() { + g.Add(-1) +} + +// Add adds fAdd to g. fAdd may be positive and negative. +// +// The g must be created with nil callback in order to be able to call this function. +func (g *Gauge) Add(fAdd float64) { + if g.f != nil { + panic(fmt.Errorf("cannot call Set on gauge created with non-nil callback")) + } + for { + n := atomic.LoadUint64(&g.valueBits) + f := math.Float64frombits(n) + fNew := f + fAdd + nNew := math.Float64bits(fNew) + if atomic.CompareAndSwapUint64(&g.valueBits, n, nNew) { + break + } + } } func (g *Gauge) marshalTo(prefix string, w io.Writer) { - v := g.f() + v := g.Get() if float64(int64(v)) == v { // Marshal integer values without scientific notation fmt.Fprintf(w, "%s %d\n", prefix, int64(v)) @@ -46,6 +97,10 @@ func (g *Gauge) marshalTo(prefix string, w io.Writer) { } } +func (g *Gauge) metricType() string { + return "gauge" +} + // GetOrCreateGauge returns registered gauge with the given name // or creates new gauge if the registry doesn't contain gauge with // the given name. @@ -53,9 +108,9 @@ func (g *Gauge) marshalTo(prefix string, w io.Writer) { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned gauge is safe to use from concurrent goroutines. // diff --git a/vendor/github.com/VictoriaMetrics/metrics/gauge_test.go b/vendor/github.com/VictoriaMetrics/metrics/gauge_test.go index 3f6e5cd0..f428fb0f 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/gauge_test.go +++ b/vendor/github.com/VictoriaMetrics/metrics/gauge_test.go @@ -7,12 +7,81 @@ import ( ) func TestGaugeError(t *testing.T) { - expectPanic(t, "NewGauge_nil_callback", func() { - NewGauge("NewGauge_nil_callback", nil) + expectPanic(t, "NewGauge_Set_non-nil-callback", func() { + g := NewGauge("NewGauge_non_nil_callback", func() float64 { return 123 }) + g.Set(12.35) }) - expectPanic(t, "GetOrCreateGauge_nil_callback", func() { - GetOrCreateGauge("GetOrCreateGauge_nil_callback", nil) + expectPanic(t, "GetOrCreateGauge_Set_non-nil-callback", func() { + g := GetOrCreateGauge("GetOrCreateGauge_nil_callback", func() float64 { return 123 }) + g.Set(42) }) + expectPanic(t, "GetOrCreateGauge_Add_non-nil-callback", func() { + g := GetOrCreateGauge("GetOrCreateGauge_nil_callback", func() float64 { return 123 }) + g.Add(42) + }) + expectPanic(t, "GetOrCreateGauge_Inc_non-nil-callback", func() { + g := GetOrCreateGauge("GetOrCreateGauge_nil_callback", func() float64 { return 123 }) + g.Inc() + }) + expectPanic(t, "GetOrCreateGauge_Dec_non-nil-callback", func() { + g := GetOrCreateGauge("GetOrCreateGauge_nil_callback", func() float64 { return 123 }) + g.Dec() + }) +} + +func TestGaugeSet(t *testing.T) { + s := NewSet() + g := s.NewGauge("foo", nil) + if n := g.Get(); n != 0 { + t.Fatalf("unexpected gauge value: %g; expecting 0", n) + } + g.Set(1.234) + if n := g.Get(); n != 1.234 { + t.Fatalf("unexpected gauge value %g; expecting 1.234", n) + } +} + +func TestGaugeIncDec(t *testing.T) { + s := NewSet() + g := s.NewGauge("foo", nil) + if n := g.Get(); n != 0 { + t.Fatalf("unexpected gauge value: %g; expecting 0", n) + } + for i := 1; i <= 100; i++ { + g.Inc() + if n := g.Get(); n != float64(i) { + t.Fatalf("unexpected gauge value %g; expecting %d", n, i) + } + } + for i := 99; i >= 0; i-- { + g.Dec() + if n := g.Get(); n != float64(i) { + t.Fatalf("unexpected gauge value %g; expecting %d", n, i) + } + } +} + +func TestGaugeIncDecConcurrenc(t *testing.T) { + s := NewSet() + g := s.NewGauge("foo", nil) + + workers := 5 + var wg sync.WaitGroup + for i := 0; i < workers; i++ { + wg.Add(1) + go func() { + for i := 0; i < 100; i++ { + g.Inc() + g.Dec() + } + wg.Done() + }() + } + wg.Wait() + + if n := g.Get(); n != 0 { + t.Fatalf("unexpected gauge value %g; want 0", n) + } } func TestGaugeSerial(t *testing.T) { diff --git a/vendor/github.com/VictoriaMetrics/metrics/go.mod b/vendor/github.com/VictoriaMetrics/metrics/go.mod index a66c19bd..64983243 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/go.mod +++ b/vendor/github.com/VictoriaMetrics/metrics/go.mod @@ -1,5 +1,10 @@ module github.com/VictoriaMetrics/metrics -require github.com/valyala/histogram v1.1.2 +require ( + github.com/valyala/histogram v1.2.0 + golang.org/x/sys v0.15.0 +) -go 1.12 +require github.com/valyala/fastrand v1.1.0 // indirect + +go 1.17 diff --git a/vendor/github.com/VictoriaMetrics/metrics/go.sum b/vendor/github.com/VictoriaMetrics/metrics/go.sum index b1219448..48326b9d 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/go.sum +++ b/vendor/github.com/VictoriaMetrics/metrics/go.sum @@ -1,4 +1,6 @@ -github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= -github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= -github.com/valyala/histogram v1.1.2 h1:vOk5VrGjMBIoPR5k6wA8vBaC8toeJ8XO0yfRjFEc1h8= -github.com/valyala/histogram v1.1.2/go.mod h1:CZAr6gK9dbD7hYx2s8WSPh0p5x5wETjC+2b3PJVtEdg= +github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= +github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= +github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= +github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/vendor/github.com/VictoriaMetrics/metrics/go_metrics.go b/vendor/github.com/VictoriaMetrics/metrics/go_metrics.go index f8b60673..2913dc79 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/go_metrics.go +++ b/vendor/github.com/VictoriaMetrics/metrics/go_metrics.go @@ -3,41 +3,78 @@ package metrics import ( "fmt" "io" + "log" + "math" "runtime" + runtimemetrics "runtime/metrics" + "strings" "github.com/valyala/histogram" ) +// See https://pkg.go.dev/runtime/metrics#hdr-Supported_metrics +var runtimeMetrics = [][2]string{ + {"/sched/latencies:seconds", "go_sched_latencies_seconds"}, + {"/sync/mutex/wait/total:seconds", "go_mutex_wait_seconds_total"}, + {"/cpu/classes/gc/mark/assist:cpu-seconds", "go_gc_mark_assist_cpu_seconds_total"}, + {"/cpu/classes/gc/total:cpu-seconds", "go_gc_cpu_seconds_total"}, + {"/gc/pauses:seconds", "go_gc_pauses_seconds"}, + {"/cpu/classes/scavenge/total:cpu-seconds", "go_scavenge_cpu_seconds_total"}, + {"/gc/gomemlimit:bytes", "go_memlimit_bytes"}, +} + +var supportedRuntimeMetrics = initSupportedRuntimeMetrics(runtimeMetrics) + +func initSupportedRuntimeMetrics(rms [][2]string) [][2]string { + exposedMetrics := make(map[string]struct{}) + for _, d := range runtimemetrics.All() { + exposedMetrics[d.Name] = struct{}{} + } + var supportedMetrics [][2]string + for _, rm := range rms { + metricName := rm[0] + if _, ok := exposedMetrics[metricName]; ok { + supportedMetrics = append(supportedMetrics, rm) + } else { + log.Printf("github.com/VictoriaMetrics/metrics: do not expose %s metric, since the corresponding metric %s isn't supported in the current Go runtime", rm[1], metricName) + } + } + return supportedMetrics +} + func writeGoMetrics(w io.Writer) { + writeRuntimeMetrics(w) + var ms runtime.MemStats runtime.ReadMemStats(&ms) - fmt.Fprintf(w, "go_memstats_alloc_bytes %d\n", ms.Alloc) - fmt.Fprintf(w, "go_memstats_alloc_bytes_total %d\n", ms.TotalAlloc) - fmt.Fprintf(w, "go_memstats_buck_hash_sys_bytes %d\n", ms.BuckHashSys) - fmt.Fprintf(w, "go_memstats_frees_total %d\n", ms.Frees) - fmt.Fprintf(w, "go_memstats_gc_cpu_fraction %g\n", ms.GCCPUFraction) - fmt.Fprintf(w, "go_memstats_gc_sys_bytes %d\n", ms.GCSys) - fmt.Fprintf(w, "go_memstats_heap_alloc_bytes %d\n", ms.HeapAlloc) - fmt.Fprintf(w, "go_memstats_heap_idle_bytes %d\n", ms.HeapIdle) - fmt.Fprintf(w, "go_memstats_heap_inuse_bytes %d\n", ms.HeapInuse) - fmt.Fprintf(w, "go_memstats_heap_objects %d\n", ms.HeapObjects) - fmt.Fprintf(w, "go_memstats_heap_released_bytes %d\n", ms.HeapReleased) - fmt.Fprintf(w, "go_memstats_heap_sys_bytes %d\n", ms.HeapSys) - fmt.Fprintf(w, "go_memstats_last_gc_time_seconds %g\n", float64(ms.LastGC)/1e9) - fmt.Fprintf(w, "go_memstats_lookups_total %d\n", ms.Lookups) - fmt.Fprintf(w, "go_memstats_mallocs_total %d\n", ms.Mallocs) - fmt.Fprintf(w, "go_memstats_mcache_inuse_bytes %d\n", ms.MCacheInuse) - fmt.Fprintf(w, "go_memstats_mcache_sys_bytes %d\n", ms.MCacheSys) - fmt.Fprintf(w, "go_memstats_mspan_inuse_bytes %d\n", ms.MSpanInuse) - fmt.Fprintf(w, "go_memstats_mspan_sys_bytes %d\n", ms.MSpanSys) - fmt.Fprintf(w, "go_memstats_next_gc_bytes %d\n", ms.NextGC) - fmt.Fprintf(w, "go_memstats_other_sys_bytes %d\n", ms.OtherSys) - fmt.Fprintf(w, "go_memstats_stack_inuse_bytes %d\n", ms.StackInuse) - fmt.Fprintf(w, "go_memstats_stack_sys_bytes %d\n", ms.StackSys) - fmt.Fprintf(w, "go_memstats_sys_bytes %d\n", ms.Sys) - - fmt.Fprintf(w, "go_cgo_calls_count %d\n", runtime.NumCgoCall()) - fmt.Fprintf(w, "go_cpu_count %d\n", runtime.NumCPU()) + WriteGaugeUint64(w, "go_memstats_alloc_bytes", ms.Alloc) + WriteCounterUint64(w, "go_memstats_alloc_bytes_total", ms.TotalAlloc) + WriteGaugeUint64(w, "go_memstats_buck_hash_sys_bytes", ms.BuckHashSys) + WriteCounterUint64(w, "go_memstats_frees_total", ms.Frees) + WriteGaugeFloat64(w, "go_memstats_gc_cpu_fraction", ms.GCCPUFraction) + WriteGaugeUint64(w, "go_memstats_gc_sys_bytes", ms.GCSys) + + WriteGaugeUint64(w, "go_memstats_heap_alloc_bytes", ms.HeapAlloc) + WriteGaugeUint64(w, "go_memstats_heap_idle_bytes", ms.HeapIdle) + WriteGaugeUint64(w, "go_memstats_heap_inuse_bytes", ms.HeapInuse) + WriteGaugeUint64(w, "go_memstats_heap_objects", ms.HeapObjects) + WriteGaugeUint64(w, "go_memstats_heap_released_bytes", ms.HeapReleased) + WriteGaugeUint64(w, "go_memstats_heap_sys_bytes", ms.HeapSys) + WriteGaugeFloat64(w, "go_memstats_last_gc_time_seconds", float64(ms.LastGC)/1e9) + WriteCounterUint64(w, "go_memstats_lookups_total", ms.Lookups) + WriteCounterUint64(w, "go_memstats_mallocs_total", ms.Mallocs) + WriteGaugeUint64(w, "go_memstats_mcache_inuse_bytes", ms.MCacheInuse) + WriteGaugeUint64(w, "go_memstats_mcache_sys_bytes", ms.MCacheSys) + WriteGaugeUint64(w, "go_memstats_mspan_inuse_bytes", ms.MSpanInuse) + WriteGaugeUint64(w, "go_memstats_mspan_sys_bytes", ms.MSpanSys) + WriteGaugeUint64(w, "go_memstats_next_gc_bytes", ms.NextGC) + WriteGaugeUint64(w, "go_memstats_other_sys_bytes", ms.OtherSys) + WriteGaugeUint64(w, "go_memstats_stack_inuse_bytes", ms.StackInuse) + WriteGaugeUint64(w, "go_memstats_stack_sys_bytes", ms.StackSys) + WriteGaugeUint64(w, "go_memstats_sys_bytes", ms.Sys) + + WriteCounterUint64(w, "go_cgo_calls_count", uint64(runtime.NumCgoCall())) + WriteGaugeUint64(w, "go_cpu_count", uint64(runtime.NumCPU())) gcPauses := histogram.NewFast() for _, pauseNs := range ms.PauseNs[:] { @@ -45,20 +82,111 @@ func writeGoMetrics(w io.Writer) { } phis := []float64{0, 0.25, 0.5, 0.75, 1} quantiles := make([]float64, 0, len(phis)) + WriteMetadataIfNeeded(w, "go_gc_duration_seconds", "summary") for i, q := range gcPauses.Quantiles(quantiles[:0], phis) { fmt.Fprintf(w, `go_gc_duration_seconds{quantile="%g"} %g`+"\n", phis[i], q) } - fmt.Fprintf(w, `go_gc_duration_seconds_sum %g`+"\n", float64(ms.PauseTotalNs)/1e9) - fmt.Fprintf(w, `go_gc_duration_seconds_count %d`+"\n", ms.NumGC) - fmt.Fprintf(w, `go_gc_forced_count %d`+"\n", ms.NumForcedGC) + fmt.Fprintf(w, "go_gc_duration_seconds_sum %g\n", float64(ms.PauseTotalNs)/1e9) + fmt.Fprintf(w, "go_gc_duration_seconds_count %d\n", ms.NumGC) + + WriteCounterUint64(w, "go_gc_forced_count", uint64(ms.NumForcedGC)) - fmt.Fprintf(w, `go_gomaxprocs %d`+"\n", runtime.GOMAXPROCS(0)) - fmt.Fprintf(w, `go_goroutines %d`+"\n", runtime.NumGoroutine()) + WriteGaugeUint64(w, "go_gomaxprocs", uint64(runtime.GOMAXPROCS(0))) + WriteGaugeUint64(w, "go_goroutines", uint64(runtime.NumGoroutine())) numThread, _ := runtime.ThreadCreateProfile(nil) - fmt.Fprintf(w, `go_threads %d`+"\n", numThread) + WriteGaugeUint64(w, "go_threads", uint64(numThread)) // Export build details. + WriteMetadataIfNeeded(w, "go_info", "gauge") fmt.Fprintf(w, "go_info{version=%q} 1\n", runtime.Version()) + + WriteMetadataIfNeeded(w, "go_info_ext", "gauge") fmt.Fprintf(w, "go_info_ext{compiler=%q, GOARCH=%q, GOOS=%q, GOROOT=%q} 1\n", runtime.Compiler, runtime.GOARCH, runtime.GOOS, runtime.GOROOT()) } + +func writeRuntimeMetrics(w io.Writer) { + samples := make([]runtimemetrics.Sample, len(supportedRuntimeMetrics)) + for i, rm := range supportedRuntimeMetrics { + samples[i].Name = rm[0] + } + runtimemetrics.Read(samples) + for i, rm := range supportedRuntimeMetrics { + writeRuntimeMetric(w, rm[1], &samples[i]) + } +} + +func writeRuntimeMetric(w io.Writer, name string, sample *runtimemetrics.Sample) { + kind := sample.Value.Kind() + switch kind { + case runtimemetrics.KindBad: + panic(fmt.Errorf("BUG: unexpected runtimemetrics.KindBad for sample.Name=%q", sample.Name)) + case runtimemetrics.KindUint64: + v := sample.Value.Uint64() + if strings.HasSuffix(name, "_total") { + WriteCounterUint64(w, name, v) + } else { + WriteGaugeUint64(w, name, v) + } + case runtimemetrics.KindFloat64: + v := sample.Value.Float64() + if isCounterName(name) { + WriteCounterFloat64(w, name, v) + } else { + WriteGaugeFloat64(w, name, v) + } + case runtimemetrics.KindFloat64Histogram: + h := sample.Value.Float64Histogram() + writeRuntimeHistogramMetric(w, name, h) + default: + panic(fmt.Errorf("unexpected metric kind=%d", kind)) + } +} + +func writeRuntimeHistogramMetric(w io.Writer, name string, h *runtimemetrics.Float64Histogram) { + buckets := h.Buckets + counts := h.Counts + if len(buckets) != len(counts)+1 { + panic(fmt.Errorf("the number of buckets must be bigger than the number of counts by 1 in histogram %s; got buckets=%d, counts=%d", name, len(buckets), len(counts))) + } + tailCount := uint64(0) + if strings.HasSuffix(name, "_seconds") { + // Limit the maximum bucket to 1 second, since Go runtime exposes buckets with 10K seconds, + // which have little sense. At the same time such buckets may lead to high cardinality issues + // at the scraper side. + for len(buckets) > 0 && buckets[len(buckets)-1] > 1 { + buckets = buckets[:len(buckets)-1] + tailCount += counts[len(counts)-1] + counts = counts[:len(counts)-1] + } + } + + iStep := float64(len(buckets)) / maxRuntimeHistogramBuckets + + totalCount := uint64(0) + iNext := 0.0 + WriteMetadataIfNeeded(w, name, "histogram") + for i, count := range counts { + totalCount += count + if float64(i) >= iNext { + iNext += iStep + le := buckets[i+1] + if !math.IsInf(le, 1) { + fmt.Fprintf(w, `%s_bucket{le="%g"} %d`+"\n", name, le, totalCount) + } + } + } + totalCount += tailCount + fmt.Fprintf(w, `%s_bucket{le="+Inf"} %d`+"\n", name, totalCount) + // _sum and _count are not exposed because the Go runtime histogram lacks accurate sum data. + // Estimating the sum (as Prometheus does) could be misleading, while exposing only `_count` without `_sum` is impractical. + // We can reconsider if precise sum data becomes available. + // + // References: + // - Go runtime histogram: https://github.com/golang/go/blob/3432c68467d50ffc622fed230a37cd401d82d4bf/src/runtime/metrics/histogram.go#L8 + // - Prometheus estimate: https://github.com/prometheus/client_golang/blob/5fe1d33cea76068edd4ece5f58e52f81d225b13c/prometheus/go_collector_latest.go#L498 + // - Related discussion: https://github.com/VictoriaMetrics/metrics/issues/94 +} + +// Limit the number of buckets for Go runtime histograms in order to prevent from high cardinality issues at scraper side. +const maxRuntimeHistogramBuckets = 30 diff --git a/vendor/github.com/VictoriaMetrics/metrics/go_metrics_test.go b/vendor/github.com/VictoriaMetrics/metrics/go_metrics_test.go new file mode 100644 index 00000000..94938147 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/go_metrics_test.go @@ -0,0 +1,71 @@ +package metrics + +import ( + "bytes" + "math" + runtimemetrics "runtime/metrics" + "strings" + "testing" +) + +func TestWriteRuntimeMetrics(t *testing.T) { + var bb bytes.Buffer + writeRuntimeMetrics(&bb) + if n := bb.Len(); n == 0 { + t.Fatalf("unexpected empty runtime metrics") + } +} + +func TestWriteRuntimeHistogramMetricOk(t *testing.T) { + f := func(h *runtimemetrics.Float64Histogram, resultExpected string) { + t.Helper() + var wOut strings.Builder + writeRuntimeHistogramMetric(&wOut, "foo", h) + result := wOut.String() + if result != resultExpected { + t.Fatalf("unexpected result; got\n%s\nwant\n%s", result, resultExpected) + } + + } + + f(&runtimemetrics.Float64Histogram{ + Counts: []uint64{1, 2, 3}, + Buckets: []float64{1, 2, 3, 4}, + }, `foo_bucket{le="2"} 1 +foo_bucket{le="3"} 3 +foo_bucket{le="4"} 6 +foo_bucket{le="+Inf"} 6 +`) + + f(&runtimemetrics.Float64Histogram{ + Counts: []uint64{0, 25, 1, 0}, + Buckets: []float64{1, 2, 3, 4, math.Inf(1)}, + }, `foo_bucket{le="2"} 0 +foo_bucket{le="3"} 25 +foo_bucket{le="4"} 26 +foo_bucket{le="+Inf"} 26 +`) + + f(&runtimemetrics.Float64Histogram{ + Counts: []uint64{0, 25, 1, 3, 0, 44, 15, 132, 10, 0}, + Buckets: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, math.Inf(1)}, + }, `foo_bucket{le="2"} 0 +foo_bucket{le="3"} 25 +foo_bucket{le="4"} 26 +foo_bucket{le="5"} 29 +foo_bucket{le="6"} 29 +foo_bucket{le="7"} 73 +foo_bucket{le="8"} 88 +foo_bucket{le="9"} 220 +foo_bucket{le="10"} 230 +foo_bucket{le="+Inf"} 230 +`) + + f(&runtimemetrics.Float64Histogram{ + Counts: []uint64{1, 5, 0}, + Buckets: []float64{math.Inf(-1), 4, 5, math.Inf(1)}, + }, `foo_bucket{le="4"} 1 +foo_bucket{le="5"} 6 +foo_bucket{le="+Inf"} 6 +`) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/histogram.go b/vendor/github.com/VictoriaMetrics/metrics/histogram.go index b0e8d575..d07ce32e 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/histogram.go +++ b/vendor/github.com/VictoriaMetrics/metrics/histogram.go @@ -25,20 +25,20 @@ var bucketMultiplier = math.Pow(10, 1.0/bucketsPerDecimal) // Each bucket contains a counter for values in the given range. // Each non-empty bucket is exposed via the following metric: // -// _bucket{,vmrange="..."} +// _bucket{,vmrange="..."} // // Where: // -// - is the metric name passed to NewHistogram -// - is optional tags for the , which are passed to NewHistogram -// - and - start and end values for the given bucket -// - - the number of hits to the given bucket during Update* calls +// - is the metric name passed to NewHistogram +// - is optional tags for the , which are passed to NewHistogram +// - and - start and end values for the given bucket +// - - the number of hits to the given bucket during Update* calls // // Histogram buckets can be converted to Prometheus-like buckets with `le` labels // with `prometheus_buckets(_bucket)` function from PromQL extensions in VictoriaMetrics. -// (see https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/MetricsQL ): +// (see https://docs.victoriametrics.com/victoriametrics/metricsql/ ): // -// prometheus_buckets(request_duration_bucket) +// prometheus_buckets(request_duration_bucket) // // Time series produced by the Histogram have better compression ratio comparing to // Prometheus histogram buckets with `le` labels, since they don't include counters @@ -46,14 +46,22 @@ var bucketMultiplier = math.Pow(10, 1.0/bucketsPerDecimal) // // Zero histogram is usable. type Histogram struct { - // Mu gurantees synchronous update for all the counters and sum. + // Mu guarantees synchronous update for all the counters and sum. + // + // Do not use sync.RWMutex, since it has zero sense from performance PoV. + // It only complicates the code. mu sync.Mutex + // decimalBuckets contains counters for histogram buckets decimalBuckets [decimalBucketsCount]*[bucketsPerDecimal]uint64 + // lower is the number of values, which hit the lower bucket lower uint64 + + // upper is the number of values, which hit the upper bucket upper uint64 + // sum is the sum of all the values put into Histogram sum float64 } @@ -109,6 +117,34 @@ func (h *Histogram) Update(v float64) { h.mu.Unlock() } +// Merge merges src to h +func (h *Histogram) Merge(src *Histogram) { + h.mu.Lock() + defer h.mu.Unlock() + + src.mu.Lock() + defer src.mu.Unlock() + + h.lower += src.lower + h.upper += src.upper + h.sum += src.sum + + for i, dbSrc := range src.decimalBuckets { + if dbSrc == nil { + continue + } + dbDst := h.decimalBuckets[i] + if dbDst == nil { + var b [bucketsPerDecimal]uint64 + dbDst = &b + h.decimalBuckets[i] = dbDst + } + for j := range dbSrc { + dbDst[j] += dbSrc[j] + } + } +} + // VisitNonZeroBuckets calls f for all buckets with non-zero counters. // // vmrange contains "..." string with bucket bounds. The lower bound @@ -143,9 +179,9 @@ func (h *Histogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned histogram is safe to use from concurrent goroutines. func NewHistogram(name string) *Histogram { @@ -159,9 +195,9 @@ func NewHistogram(name string) *Histogram { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned histogram is safe to use from concurrent goroutines. // @@ -228,3 +264,7 @@ func (h *Histogram) getSum() float64 { h.mu.Unlock() return sum } + +func (h *Histogram) metricType() string { + return "histogram" +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/histogram_example_test.go b/vendor/github.com/VictoriaMetrics/metrics/histogram_example_test.go index 358050e1..e100fc47 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/histogram_example_test.go +++ b/vendor/github.com/VictoriaMetrics/metrics/histogram_example_test.go @@ -20,7 +20,7 @@ func ExampleHistogram() { func ExampleHistogram_vec() { for i := 0; i < 3; i++ { // Dynamically construct metric name and pass it to GetOrCreateHistogram. - name := fmt.Sprintf(`response_size_bytes{path=%q}`, "/foo/bar") + name := fmt.Sprintf(`response_size_bytes{path=%q, code=%q}`, "/foo/bar", 200+i) response := processRequest() metrics.GetOrCreateHistogram(name).Update(float64(len(response))) } diff --git a/vendor/github.com/VictoriaMetrics/metrics/histogram_test.go b/vendor/github.com/VictoriaMetrics/metrics/histogram_test.go index dea989bf..e3b20f1c 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/histogram_test.go +++ b/vendor/github.com/VictoriaMetrics/metrics/histogram_test.go @@ -10,6 +10,35 @@ import ( "time" ) +func TestHistogramMerge(t *testing.T) { + name := `TestHistogramMerge` + h := NewHistogram(name) + // Write data to histogram + for i := 98; i < 218; i++ { + h.Update(float64(i)) + } + + b := NewHistogram("test") + for i := 98; i < 218; i++ { + b.Update(float64(i)) + } + + h.Merge(b) + + // Make sure the histogram prints _bucket on marshalTo call + testMarshalTo(t, h, "prefix", `prefix_bucket{vmrange="8.799e+01...1.000e+02"} 6 +prefix_bucket{vmrange="1.000e+02...1.136e+02"} 26 +prefix_bucket{vmrange="1.136e+02...1.292e+02"} 32 +prefix_bucket{vmrange="1.292e+02...1.468e+02"} 34 +prefix_bucket{vmrange="1.468e+02...1.668e+02"} 40 +prefix_bucket{vmrange="1.668e+02...1.896e+02"} 46 +prefix_bucket{vmrange="1.896e+02...2.154e+02"} 52 +prefix_bucket{vmrange="2.154e+02...2.448e+02"} 4 +prefix_sum 37800 +prefix_count 240 +`) +} + func TestGetVMRange(t *testing.T) { f := func(bucketIdx int, vmrangeExpected string) { t.Helper() @@ -171,6 +200,20 @@ func TestHistogramWithTags(t *testing.T) { } } +func TestHistogramWithEmptyTags(t *testing.T) { + name := `TestHistogram{}` + h := NewHistogram(name) + h.Update(123) + + var bb bytes.Buffer + WritePrometheus(&bb, false) + result := bb.String() + namePrefixWithTag := `TestHistogram_bucket{vmrange="1.136e+02...1.292e+02"} 1` + "\n" + if !strings.Contains(result, namePrefixWithTag) { + t.Fatalf("missing histogram %s in the WritePrometheus output; got\n%s", namePrefixWithTag, result) + } +} + func TestGetOrCreateHistogramSerial(t *testing.T) { name := "GetOrCreateHistogramSerial" if err := testGetOrCreateHistogram(name); err != nil { diff --git a/vendor/github.com/VictoriaMetrics/metrics/metrics.go b/vendor/github.com/VictoriaMetrics/metrics/metrics.go index c28c0361..3af67054 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/metrics.go +++ b/vendor/github.com/VictoriaMetrics/metrics/metrics.go @@ -5,41 +5,106 @@ // // Usage: // -// 1. Register the required metrics via New* functions. -// 2. Expose them to `/metrics` page via WritePrometheus. -// 3. Update the registered metrics during application lifetime. +// 1. Register the required metrics via New* functions. +// 2. Expose them to `/metrics` page via WritePrometheus. +// 3. Update the registered metrics during application lifetime. // // The package has been extracted from https://victoriametrics.com/ package metrics import ( + "fmt" "io" + "sort" + "strings" + "sync" + "sync/atomic" + "unsafe" ) type namedMetric struct { name string metric metric + isAux bool } type metric interface { marshalTo(prefix string, w io.Writer) + metricType() string } var defaultSet = NewSet() -// WritePrometheus writes all the registered metrics in Prometheus format to w. +func init() { + RegisterSet(defaultSet) +} + +var ( + registeredSets = make(map[*Set]struct{}) + registeredSetsLock sync.Mutex +) + +// RegisterSet registers the given set s for metrics export via global WritePrometheus() call. +// +// See also UnregisterSet. +func RegisterSet(s *Set) { + registeredSetsLock.Lock() + registeredSets[s] = struct{}{} + registeredSetsLock.Unlock() +} + +// UnregisterSet stops exporting metrics for the given s via global WritePrometheus() call. +// +// If destroySet is set to true, then s.UnregisterAllMetrics() is called on s after unregistering it, +// so s becomes destroyed. Otherwise the s can be registered again in the set by passing it to RegisterSet(). +func UnregisterSet(s *Set, destroySet bool) { + registeredSetsLock.Lock() + delete(registeredSets, s) + registeredSetsLock.Unlock() + + if destroySet { + s.UnregisterAllMetrics() + } +} + +// RegisterMetricsWriter registers writeMetrics callback for including metrics in the output generated by WritePrometheus. +// +// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments. +// The last line generated by writeMetrics must end with \n. +// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format +// +// It is OK to register multiple writeMetrics callbacks - all of them will be called sequentially for gererating the output at WritePrometheus. +func RegisterMetricsWriter(writeMetrics func(w io.Writer)) { + defaultSet.RegisterMetricsWriter(writeMetrics) +} + +// WritePrometheus writes all the metrics in Prometheus format from the default set, all the added sets and metrics writers to w. +// +// Additional sets can be registered via RegisterSet() call. +// Additional metric writers can be registered via RegisterMetricsWriter() call. // // If exposeProcessMetrics is true, then various `go_*` and `process_*` metrics // are exposed for the current process. // // The WritePrometheus func is usually called inside "/metrics" handler: // -// http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { -// metrics.WritePrometheus(w, true) -// }) -// +// http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { +// metrics.WritePrometheus(w, true) +// }) func WritePrometheus(w io.Writer, exposeProcessMetrics bool) { - defaultSet.WritePrometheus(w) + registeredSetsLock.Lock() + sets := make([]*Set, 0, len(registeredSets)) + for s := range registeredSets { + sets = append(sets, s) + } + registeredSetsLock.Unlock() + + sort.Slice(sets, func(i, j int) bool { + return uintptr(unsafe.Pointer(sets[i])) < uintptr(unsafe.Pointer(sets[j])) + }) + for _, s := range sets { + s.WritePrometheus(w) + } if exposeProcessMetrics { WriteProcessMetrics(w) } @@ -50,55 +115,137 @@ func WritePrometheus(w io.Writer, exposeProcessMetrics bool) { // The following `go_*` and `process_*` metrics are exposed for the currently // running process. Below is a short description for the exposed `process_*` metrics: // -// - process_cpu_seconds_system_total - CPU time spent in syscalls -// - process_cpu_seconds_user_total - CPU time spent in userspace -// - process_cpu_seconds_total - CPU time spent by the process -// - process_major_pagefaults_total - page faults resulted in disk IO -// - process_minor_pagefaults_total - page faults resolved without disk IO -// - process_resident_memory_bytes - recently accessed memory (aka RSS or resident memory) -// - process_resident_memory_peak_bytes - the maximum RSS memory usage -// - process_resident_memory_anon_bytes - RSS for memory-mapped files -// - process_resident_memory_file_bytes - RSS for memory allocated by the process -// - process_resident_memory_shared_bytes - RSS for memory shared between multiple processes -// - process_virtual_memory_bytes - virtual memory usage -// - process_virtual_memory_peak_bytes - the maximum virtual memory usage -// - process_num_threads - the number of threads -// - process_start_time_seconds - process start time as unix timestamp -// -// - process_io_read_bytes_total - the number of bytes read via syscalls -// - process_io_written_bytes_total - the number of bytes written via syscalls -// - process_io_read_syscalls_total - the number of read syscalls -// - process_io_write_syscalls_total - the number of write syscalls -// - process_io_storage_read_bytes_total - the number of bytes actually read from disk -// - process_io_storage_written_bytes_total - the number of bytes actually written to disk -// -// - go_memstats_alloc_bytes - memory usage for Go objects in the heap -// - go_memstats_alloc_bytes_total - the cumulative counter for total size of allocated Go objects -// - go_memstats_frees_total - the cumulative counter for number of freed Go objects -// - go_memstats_gc_cpu_fraction - the fraction of CPU spent in Go garbage collector -// - go_memstats_gc_sys_bytes - the size of Go garbage collector metadata -// - go_memstats_heap_alloc_bytes - the same as go_memstats_alloc_bytes -// - go_memstats_heap_idle_bytes - idle memory ready for new Go object allocations -// - go_memstats_heap_objects - the number of Go objects in the heap -// - go_memstats_heap_sys_bytes - memory requested for Go objects from the OS -// - go_memstats_mallocs_total - the number of allocations for Go objects -// - go_memstats_next_gc_bytes - the target heap size when the next garbage collection should start -// - go_memstats_stack_inuse_bytes - memory used for goroutine stacks -// - go_memstats_stack_sys_bytes - memory requested fromthe OS for goroutine stacks -// - go_memstats_sys_bytes - memory requested by Go runtime from the OS +// - process_cpu_seconds_system_total - CPU time spent in syscalls +// +// - process_cpu_seconds_user_total - CPU time spent in userspace +// +// - process_cpu_seconds_total - CPU time spent by the process +// +// - process_major_pagefaults_total - page faults resulted in disk IO +// +// - process_minor_pagefaults_total - page faults resolved without disk IO +// +// - process_resident_memory_bytes - recently accessed memory (aka RSS or resident memory) +// +// - process_resident_memory_peak_bytes - the maximum RSS memory usage +// +// - process_resident_memory_anon_bytes - RSS for memory-mapped files +// +// - process_resident_memory_file_bytes - RSS for memory allocated by the process +// +// - process_resident_memory_shared_bytes - RSS for memory shared between multiple processes +// +// - process_virtual_memory_bytes - virtual memory usage +// +// - process_virtual_memory_peak_bytes - the maximum virtual memory usage +// +// - process_num_threads - the number of threads +// +// - process_start_time_seconds - process start time as unix timestamp +// +// - process_io_read_bytes_total - the number of bytes read via syscalls +// +// - process_io_written_bytes_total - the number of bytes written via syscalls +// +// - process_io_read_syscalls_total - the number of read syscalls +// +// - process_io_write_syscalls_total - the number of write syscalls +// +// - process_io_storage_read_bytes_total - the number of bytes actually read from disk +// +// - process_io_storage_written_bytes_total - the number of bytes actually written to disk +// +// - process_pressure_cpu_waiting_seconds_total - the number of seconds processes in the current cgroup v2 were waiting to be executed +// +// - process_pressure_cpu_stalled_seconds_total - the number of seconds all the processes in the current cgroup v2 were stalled +// +// - process_pressure_io_waiting_seconds_total - the number of seconds processes in the current cgroup v2 were waiting for io to complete +// +// - process_pressure_io_stalled_seconds_total - the number of seconds all the processes in the current cgroup v2 were waiting for io to complete +// +// - process_pressure_memory_waiting_seconds_total - the number of seconds processes in the current cgroup v2 were waiting for memory access to complete +// +// - process_pressure_memory_stalled_seconds_total - the number of seconds all the processes in the current cgroup v2 were waiting for memory access to complete +// +// - go_sched_latencies_seconds - time spent by goroutines in ready state before they start execution +// +// - go_mutex_wait_seconds_total - summary time spent by all the goroutines while waiting for locked mutex +// +// - go_gc_mark_assist_cpu_seconds_total - summary CPU time spent by goroutines in GC mark assist state +// +// - go_gc_cpu_seconds_total - summary time spent in GC +// +// - go_gc_pauses_seconds - duration of GC pauses +// +// - go_scavenge_cpu_seconds_total - CPU time spent on returning the memory to OS +// +// - go_memlimit_bytes - the GOMEMLIMIT env var value +// +// - go_memstats_alloc_bytes - memory usage for Go objects in the heap +// +// - go_memstats_alloc_bytes_total - the cumulative counter for total size of allocated Go objects +// +// - go_memstats_buck_hash_sys_bytes - bytes of memory in profiling bucket hash tables +// +// - go_memstats_frees_total - the cumulative counter for number of freed Go objects +// +// - go_memstats_gc_cpu_fraction - the fraction of CPU spent in Go garbage collector +// +// - go_memstats_gc_sys_bytes - the size of Go garbage collector metadata +// +// - go_memstats_heap_alloc_bytes - the same as go_memstats_alloc_bytes +// +// - go_memstats_heap_idle_bytes - idle memory ready for new Go object allocations +// +// - go_memstats_heap_inuse_bytes - bytes in in-use spans +// +// - go_memstats_heap_objects - the number of Go objects in the heap +// +// - go_memstats_heap_released_bytes - bytes of physical memory returned to the OS +// +// - go_memstats_heap_sys_bytes - memory requested for Go objects from the OS +// +// - go_memstats_last_gc_time_seconds - unix timestamp the last garbage collection finished +// +// - go_memstats_lookups_total - the number of pointer lookups performed by the runtime +// +// - go_memstats_mallocs_total - the number of allocations for Go objects +// +// - go_memstats_mcache_inuse_bytes - bytes of allocated mcache structures +// +// - go_memstats_mcache_sys_bytes - bytes of memory obtained from the OS for mcache structures +// +// - go_memstats_mspan_inuse_bytes - bytes of allocated mspan structures +// +// - go_memstats_mspan_sys_bytes - bytes of memory obtained from the OS for mspan structures +// +// - go_memstats_next_gc_bytes - the target heap size when the next garbage collection should start +// +// - go_memstats_other_sys_bytes - bytes of memory in miscellaneous off-heap runtime allocations +// +// - go_memstats_stack_inuse_bytes - memory used for goroutine stacks +// +// - go_memstats_stack_sys_bytes - memory requested fromthe OS for goroutine stacks +// +// - go_memstats_sys_bytes - memory requested by Go runtime from the OS +// +// - go_cgo_calls_count - the total number of CGO calls +// +// - go_cpu_count - the number of CPU cores on the host where the app runs // // The WriteProcessMetrics func is usually called in combination with writing Set metrics // inside "/metrics" handler: // -// http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { -// mySet.WritePrometheus(w) -// metrics.WriteProcessMetrics(w) -// }) +// http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { +// mySet.WritePrometheus(w) +// metrics.WriteProcessMetrics(w) +// }) // -// See also WrteFDMetrics. +// See also WriteFDMetrics. func WriteProcessMetrics(w io.Writer) { writeGoMetrics(w) writeProcessMetrics(w) + writePushMetrics(w) } // WriteFDMetrics writes `process_max_fds` and `process_open_fds` metrics to w. @@ -107,6 +254,98 @@ func WriteFDMetrics(w io.Writer) { } // UnregisterMetric removes metric with the given name from default set. +// +// See also UnregisterAllMetrics. func UnregisterMetric(name string) bool { return defaultSet.UnregisterMetric(name) } + +// UnregisterAllMetrics unregisters all the metrics from default set. +// +// It also unregisters writeMetrics callbacks passed to RegisterMetricsWriter. +func UnregisterAllMetrics() { + defaultSet.UnregisterAllMetrics() +} + +// ListMetricNames returns sorted list of all the metric names from default set. +func ListMetricNames() []string { + return defaultSet.ListMetricNames() +} + +// GetDefaultSet returns the default metrics set. +func GetDefaultSet() *Set { + return defaultSet +} + +// ExposeMetadata allows enabling adding TYPE and HELP metadata to the exposed metrics globally. +// +// It is safe to call this method multiple times. It is allowed to change it in runtime. +// ExposeMetadata is set to false by default. +func ExposeMetadata(v bool) { + n := 0 + if v { + n = 1 + } + atomic.StoreUint32(&exposeMetadata, uint32(n)) +} + +func isMetadataEnabled() bool { + n := atomic.LoadUint32(&exposeMetadata) + return n != 0 +} + +var exposeMetadata uint32 + +func isCounterName(name string) bool { + return strings.HasSuffix(name, "_total") +} + +// WriteGaugeUint64 writes gauge metric with the given name and value to w in Prometheus text exposition format. +func WriteGaugeUint64(w io.Writer, name string, value uint64) { + writeMetricUint64(w, name, "gauge", value) +} + +// WriteGaugeFloat64 writes gauge metric with the given name and value to w in Prometheus text exposition format. +func WriteGaugeFloat64(w io.Writer, name string, value float64) { + writeMetricFloat64(w, name, "gauge", value) +} + +// WriteCounterUint64 writes counter metric with the given name and value to w in Prometheus text exposition format. +func WriteCounterUint64(w io.Writer, name string, value uint64) { + writeMetricUint64(w, name, "counter", value) +} + +// WriteCounterFloat64 writes counter metric with the given name and value to w in Prometheus text exposition format. +func WriteCounterFloat64(w io.Writer, name string, value float64) { + writeMetricFloat64(w, name, "counter", value) +} + +func writeMetricUint64(w io.Writer, metricName, metricType string, value uint64) { + WriteMetadataIfNeeded(w, metricName, metricType) + fmt.Fprintf(w, "%s %d\n", metricName, value) +} + +func writeMetricFloat64(w io.Writer, metricName, metricType string, value float64) { + WriteMetadataIfNeeded(w, metricName, metricType) + fmt.Fprintf(w, "%s %g\n", metricName, value) +} + +// WriteMetadataIfNeeded writes HELP and TYPE metadata for the given metricName and metricType if this is globally enabled via ExposeMetadata(). +// +// If the metadata exposition isn't enabled, then this function is no-op. +func WriteMetadataIfNeeded(w io.Writer, metricName, metricType string) { + if !isMetadataEnabled() { + return + } + metricFamily := getMetricFamily(metricName) + fmt.Fprintf(w, "# HELP %s\n", metricFamily) + fmt.Fprintf(w, "# TYPE %s %s\n", metricFamily, metricType) +} + +func getMetricFamily(metricName string) string { + n := strings.IndexByte(metricName, '{') + if n < 0 { + return metricName + } + return metricName[:n] +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/metrics_test.go b/vendor/github.com/VictoriaMetrics/metrics/metrics_test.go index baa624c2..7cba9e9e 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/metrics_test.go +++ b/vendor/github.com/VictoriaMetrics/metrics/metrics_test.go @@ -3,10 +3,160 @@ package metrics import ( "bytes" "fmt" + "io" + "strings" "testing" "time" ) +func TestWriteMetrics(t *testing.T) { + t.Run("gauge_uint64", func(t *testing.T) { + var bb bytes.Buffer + + WriteGaugeUint64(&bb, "foo", 123) + sExpected := "foo 123\n" + if s := bb.String(); s != sExpected { + t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected) + } + + ExposeMetadata(true) + bb.Reset() + WriteGaugeUint64(&bb, "foo", 123) + sExpected = "# HELP foo\n# TYPE foo gauge\nfoo 123\n" + ExposeMetadata(false) + if s := bb.String(); s != sExpected { + t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected) + } + }) + t.Run("gauge_float64", func(t *testing.T) { + var bb bytes.Buffer + + WriteGaugeFloat64(&bb, "foo", 1.23) + sExpected := "foo 1.23\n" + if s := bb.String(); s != sExpected { + t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected) + } + + ExposeMetadata(true) + bb.Reset() + WriteGaugeFloat64(&bb, "foo", 1.23) + sExpected = "# HELP foo\n# TYPE foo gauge\nfoo 1.23\n" + ExposeMetadata(false) + if s := bb.String(); s != sExpected { + t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected) + } + }) + t.Run("counter_uint64", func(t *testing.T) { + var bb bytes.Buffer + + WriteCounterUint64(&bb, "foo_total", 123) + sExpected := "foo_total 123\n" + if s := bb.String(); s != sExpected { + t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected) + } + + ExposeMetadata(true) + bb.Reset() + WriteCounterUint64(&bb, "foo_total", 123) + sExpected = "# HELP foo_total\n# TYPE foo_total counter\nfoo_total 123\n" + ExposeMetadata(false) + if s := bb.String(); s != sExpected { + t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected) + } + }) + t.Run("counter_float64", func(t *testing.T) { + var bb bytes.Buffer + + WriteCounterFloat64(&bb, "foo_total", 1.23) + sExpected := "foo_total 1.23\n" + if s := bb.String(); s != sExpected { + t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected) + } + + ExposeMetadata(true) + bb.Reset() + WriteCounterFloat64(&bb, "foo_total", 1.23) + sExpected = "# HELP foo_total\n# TYPE foo_total counter\nfoo_total 1.23\n" + ExposeMetadata(false) + if s := bb.String(); s != sExpected { + t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected) + } + }) +} + +func TestGetDefaultSet(t *testing.T) { + s := GetDefaultSet() + if s != defaultSet { + t.Fatalf("GetDefaultSet must return defaultSet=%p, but returned %p", defaultSet, s) + } +} + +func TestUnregisterAllMetrics(t *testing.T) { + for j := 0; j < 3; j++ { + for i := 0; i < 10; i++ { + _ = NewCounter(fmt.Sprintf("counter_%d", i)) + _ = NewSummary(fmt.Sprintf("summary_%d", i)) + _ = NewHistogram(fmt.Sprintf("histogram_%d", i)) + _ = NewGauge(fmt.Sprintf("gauge_%d", i), func() float64 { return 0 }) + } + if mns := ListMetricNames(); len(mns) == 0 { + t.Fatalf("unexpected empty list of metrics on iteration %d", j) + } + UnregisterAllMetrics() + if mns := ListMetricNames(); len(mns) != 0 { + t.Fatalf("unexpected metric names after UnregisterAllMetrics call on iteration %d: %q", j, mns) + } + } +} + +func TestRegisterMetricsWriter(t *testing.T) { + RegisterMetricsWriter(func(w io.Writer) { + WriteCounterUint64(w, `counter{label="abc"}`, 1234) + WriteGaugeFloat64(w, `gauge{a="b",c="d"}`, -34.43) + }) + + var bb bytes.Buffer + WritePrometheus(&bb, false) + data := bb.String() + + UnregisterAllMetrics() + + expectedLine := fmt.Sprintf(`counter{label="abc"} 1234` + "\n") + if !strings.Contains(data, expectedLine) { + t.Fatalf("missing %q in\n%s", expectedLine, data) + } + + expectedLine = fmt.Sprintf(`gauge{a="b",c="d"} -34.43` + "\n") + if !strings.Contains(data, expectedLine) { + t.Fatalf("missing %q in\n%s", expectedLine, data) + } +} + +func TestRegisterUnregisterSet(t *testing.T) { + const metricName = "metric_from_set" + const metricValue = 123 + s := NewSet() + c := s.NewCounter(metricName) + c.Set(metricValue) + + RegisterSet(s) + var bb bytes.Buffer + WritePrometheus(&bb, false) + data := bb.String() + expectedLine := fmt.Sprintf("%s %d\n", metricName, metricValue) + if !strings.Contains(data, expectedLine) { + t.Fatalf("missing %q in\n%s", expectedLine, data) + } + + UnregisterSet(s, true) + bb.Reset() + WritePrometheus(&bb, false) + data = bb.String() + if strings.Contains(data, expectedLine) { + t.Fatalf("unepected %q in\n%s", expectedLine, data) + } +} + func TestInvalidName(t *testing.T) { f := func(name string) { t.Helper() diff --git a/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go index 12b5de8e..95ce6efe 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go +++ b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go @@ -9,12 +9,18 @@ import ( "os" "strconv" "strings" + "sync/atomic" "time" ) // See https://github.com/prometheus/procfs/blob/a4ac0826abceb44c40fc71daed2b301db498b93e/proc_stat.go#L40 . const userHZ = 100 +// Different environments may have different page size. +// +// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6457 +var pageSizeBytes = uint64(os.Getpagesize()) + // See http://man7.org/linux/man-pages/man5/proc.5.html type procStat struct { State byte @@ -45,13 +51,14 @@ func writeProcessMetrics(w io.Writer) { statFilepath := "/proc/self/stat" data, err := ioutil.ReadFile(statFilepath) if err != nil { - log.Printf("ERROR: cannot open %s: %s", statFilepath, err) + log.Printf("ERROR: metrics: cannot open %s: %s", statFilepath, err) return } + // Search for the end of command. n := bytes.LastIndex(data, []byte(") ")) if n < 0 { - log.Printf("ERROR: cannot find command in parentheses in %q read from %s", data, statFilepath) + log.Printf("ERROR: metrics: cannot find command in parentheses in %q read from %s", data, statFilepath) return } data = data[n+2:] @@ -62,7 +69,7 @@ func writeProcessMetrics(w io.Writer) { &p.State, &p.Ppid, &p.Pgrp, &p.Session, &p.TtyNr, &p.Tpgid, &p.Flags, &p.Minflt, &p.Cminflt, &p.Majflt, &p.Cmajflt, &p.Utime, &p.Stime, &p.Cutime, &p.Cstime, &p.Priority, &p.Nice, &p.NumThreads, &p.ItrealValue, &p.Starttime, &p.Vsize, &p.Rss) if err != nil { - log.Printf("ERROR: cannot parse %q read from %s: %s", data, statFilepath, err) + log.Printf("ERROR: metrics: cannot parse %q read from %s: %s", data, statFilepath, err) return } @@ -72,34 +79,43 @@ func writeProcessMetrics(w io.Writer) { utime := float64(p.Utime) / userHZ stime := float64(p.Stime) / userHZ - fmt.Fprintf(w, "process_cpu_seconds_system_total %g\n", stime) - fmt.Fprintf(w, "process_cpu_seconds_total %g\n", utime+stime) - fmt.Fprintf(w, "process_cpu_seconds_user_total %g\n", utime) - fmt.Fprintf(w, "process_major_pagefaults_total %d\n", p.Majflt) - fmt.Fprintf(w, "process_minor_pagefaults_total %d\n", p.Minflt) - fmt.Fprintf(w, "process_num_threads %d\n", p.NumThreads) - fmt.Fprintf(w, "process_resident_memory_bytes %d\n", p.Rss*4096) - fmt.Fprintf(w, "process_start_time_seconds %d\n", startTimeSeconds) - fmt.Fprintf(w, "process_virtual_memory_bytes %d\n", p.Vsize) + WriteCounterFloat64(w, "process_cpu_seconds_system_total", stime) + WriteCounterFloat64(w, "process_cpu_seconds_total", utime+stime) + WriteCounterFloat64(w, "process_cpu_seconds_user_total", utime) + WriteCounterUint64(w, "process_major_pagefaults_total", uint64(p.Majflt)) + WriteCounterUint64(w, "process_minor_pagefaults_total", uint64(p.Minflt)) + WriteGaugeUint64(w, "process_num_threads", uint64(p.NumThreads)) + WriteGaugeUint64(w, "process_resident_memory_bytes", uint64(p.Rss)*pageSizeBytes) + WriteGaugeUint64(w, "process_start_time_seconds", uint64(startTimeSeconds)) + WriteGaugeUint64(w, "process_virtual_memory_bytes", uint64(p.Vsize)) writeProcessMemMetrics(w) writeIOMetrics(w) + writePSIMetrics(w) } +var procSelfIOErrLogged uint32 + func writeIOMetrics(w io.Writer) { ioFilepath := "/proc/self/io" data, err := ioutil.ReadFile(ioFilepath) if err != nil { - log.Printf("ERROR: cannot open %q: %s", ioFilepath, err) + // Do not spam the logs with errors - this error cannot be fixed without process restart. + // See https://github.com/VictoriaMetrics/metrics/issues/42 + if atomic.CompareAndSwapUint32(&procSelfIOErrLogged, 0, 1) { + log.Printf("ERROR: metrics: cannot read process_io_* metrics from %q, so these metrics won't be updated until the error is fixed; "+ + "see https://github.com/VictoriaMetrics/metrics/issues/42 ; The error: %s", ioFilepath, err) + } } + getInt := func(s string) int64 { n := strings.IndexByte(s, ' ') if n < 0 { - log.Printf("ERROR: cannot find whitespace in %q at %q", s, ioFilepath) + log.Printf("ERROR: metrics: cannot find whitespace in %q at %q", s, ioFilepath) return 0 } v, err := strconv.ParseInt(s[n+1:], 10, 64) if err != nil { - log.Printf("ERROR: cannot parse %q at %q: %s", s, ioFilepath, err) + log.Printf("ERROR: metrics: cannot parse %q at %q: %s", s, ioFilepath, err) return 0 } return v @@ -123,12 +139,12 @@ func writeIOMetrics(w io.Writer) { writeBytes = getInt(s) } } - fmt.Fprintf(w, "process_io_read_bytes_total %d\n", rchar) - fmt.Fprintf(w, "process_io_written_bytes_total %d\n", wchar) - fmt.Fprintf(w, "process_io_read_syscalls_total %d\n", syscr) - fmt.Fprintf(w, "process_io_write_syscalls_total %d\n", syscw) - fmt.Fprintf(w, "process_io_storage_read_bytes_total %d\n", readBytes) - fmt.Fprintf(w, "process_io_storage_written_bytes_total %d\n", writeBytes) + WriteGaugeUint64(w, "process_io_read_bytes_total", uint64(rchar)) + WriteGaugeUint64(w, "process_io_written_bytes_total", uint64(wchar)) + WriteGaugeUint64(w, "process_io_read_syscalls_total", uint64(syscr)) + WriteGaugeUint64(w, "process_io_write_syscalls_total", uint64(syscw)) + WriteGaugeUint64(w, "process_io_storage_read_bytes_total", uint64(readBytes)) + WriteGaugeUint64(w, "process_io_storage_written_bytes_total", uint64(writeBytes)) } var startTimeSeconds = time.Now().Unix() @@ -137,16 +153,16 @@ var startTimeSeconds = time.Now().Unix() func writeFDMetrics(w io.Writer) { totalOpenFDs, err := getOpenFDsCount("/proc/self/fd") if err != nil { - log.Printf("ERROR: cannot determine open file descriptors count: %s", err) + log.Printf("ERROR: metrics: cannot determine open file descriptors count: %s", err) return } maxOpenFDs, err := getMaxFilesLimit("/proc/self/limits") if err != nil { - log.Printf("ERROR: cannot determine the limit on open file descritors: %s", err) + log.Printf("ERROR: metrics: cannot determine the limit on open file descritors: %s", err) return } - fmt.Fprintf(w, "process_max_fds %d\n", maxOpenFDs) - fmt.Fprintf(w, "process_open_fds %d\n", totalOpenFDs) + WriteGaugeUint64(w, "process_max_fds", maxOpenFDs) + WriteGaugeUint64(w, "process_open_fds", totalOpenFDs) } func getOpenFDsCount(path string) (uint64, error) { @@ -211,14 +227,14 @@ type memStats struct { func writeProcessMemMetrics(w io.Writer) { ms, err := getMemStats("/proc/self/status") if err != nil { - log.Printf("ERROR: cannot determine memory status: %s", err) + log.Printf("ERROR: metrics: cannot determine memory status: %s", err) return } - fmt.Fprintf(w, "process_virtual_memory_peak_bytes %d\n", ms.vmPeak) - fmt.Fprintf(w, "process_resident_memory_peak_bytes %d\n", ms.rssPeak) - fmt.Fprintf(w, "process_resident_memory_anon_bytes %d\n", ms.rssAnon) - fmt.Fprintf(w, "process_resident_memory_file_bytes %d\n", ms.rssFile) - fmt.Fprintf(w, "process_resident_memory_shared_bytes %d\n", ms.rssShmem) + WriteGaugeUint64(w, "process_virtual_memory_peak_bytes", ms.vmPeak) + WriteGaugeUint64(w, "process_resident_memory_peak_bytes", ms.rssPeak) + WriteGaugeUint64(w, "process_resident_memory_anon_bytes", ms.rssAnon) + WriteGaugeUint64(w, "process_resident_memory_file_bytes", ms.rssFile) + WriteGaugeUint64(w, "process_resident_memory_shared_bytes", ms.rssShmem) } @@ -263,3 +279,134 @@ func getMemStats(path string) (*memStats, error) { } return &ms, nil } + +// writePSIMetrics writes PSI total metrics for the current process to w. +// +// See https://docs.kernel.org/accounting/psi.html +func writePSIMetrics(w io.Writer) { + if psiMetricsStart == nil { + // Failed to initialize PSI metrics + return + } + + m, err := getPSIMetrics() + if err != nil { + log.Printf("ERROR: metrics: cannot expose PSI metrics: %s", err) + return + } + + WriteCounterFloat64(w, "process_pressure_cpu_waiting_seconds_total", psiTotalSecs(m.cpuSome-psiMetricsStart.cpuSome)) + WriteCounterFloat64(w, "process_pressure_cpu_stalled_seconds_total", psiTotalSecs(m.cpuFull-psiMetricsStart.cpuFull)) + + WriteCounterFloat64(w, "process_pressure_io_waiting_seconds_total", psiTotalSecs(m.ioSome-psiMetricsStart.ioSome)) + WriteCounterFloat64(w, "process_pressure_io_stalled_seconds_total", psiTotalSecs(m.ioFull-psiMetricsStart.ioFull)) + + WriteCounterFloat64(w, "process_pressure_memory_waiting_seconds_total", psiTotalSecs(m.memSome-psiMetricsStart.memSome)) + WriteCounterFloat64(w, "process_pressure_memory_stalled_seconds_total", psiTotalSecs(m.memFull-psiMetricsStart.memFull)) +} + +func psiTotalSecs(microsecs uint64) float64 { + // PSI total stats is in microseconds according to https://docs.kernel.org/accounting/psi.html + // Convert it to seconds. + return float64(microsecs) / 1e6 +} + +// psiMetricsStart contains the initial PSI metric values on program start. +// it is needed in order to make sure the exposed PSI metrics start from zero. +var psiMetricsStart = func() *psiMetrics { + m, err := getPSIMetrics() + if err != nil { + log.Printf("ERROR: metrics: disable exposing PSI metrics because of failed init: %s", err) + return nil + } + return m +}() + +type psiMetrics struct { + cpuSome uint64 + cpuFull uint64 + ioSome uint64 + ioFull uint64 + memSome uint64 + memFull uint64 +} + +func getPSIMetrics() (*psiMetrics, error) { + cgroupPath := getCgroupV2Path() + if cgroupPath == "" { + // Do nothing, since PSI requires cgroup v2, and the process doesn't run under cgroup v2. + return nil, nil + } + + cpuSome, cpuFull, err := readPSITotals(cgroupPath, "cpu.pressure") + if err != nil { + return nil, err + } + + ioSome, ioFull, err := readPSITotals(cgroupPath, "io.pressure") + if err != nil { + return nil, err + } + + memSome, memFull, err := readPSITotals(cgroupPath, "memory.pressure") + if err != nil { + return nil, err + } + + m := &psiMetrics{ + cpuSome: cpuSome, + cpuFull: cpuFull, + ioSome: ioSome, + ioFull: ioFull, + memSome: memSome, + memFull: memFull, + } + return m, nil +} + +func readPSITotals(cgroupPath, statsName string) (uint64, uint64, error) { + filePath := cgroupPath + "/" + statsName + data, err := ioutil.ReadFile(filePath) + if err != nil { + return 0, 0, err + } + + lines := strings.Split(string(data), "\n") + some := uint64(0) + full := uint64(0) + for _, line := range lines { + line = strings.TrimSpace(line) + if !strings.HasPrefix(line, "some ") && !strings.HasPrefix(line, "full ") { + continue + } + + tmp := strings.SplitN(line, "total=", 2) + if len(tmp) != 2 { + return 0, 0, fmt.Errorf("cannot find total from the line %q at %q", line, filePath) + } + microsecs, err := strconv.ParseUint(tmp[1], 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("cannot parse total=%q at %q: %w", tmp[1], filePath, err) + } + + switch { + case strings.HasPrefix(line, "some "): + some = microsecs + case strings.HasPrefix(line, "full "): + full = microsecs + } + } + return some, full, nil +} + +func getCgroupV2Path() string { + data, err := ioutil.ReadFile("/proc/self/cgroup") + if err != nil { + return "" + } + tmp := strings.SplitN(string(data), "::", 2) + if len(tmp) != 2 { + return "" + } + return "/sys/fs/cgroup" + strings.TrimSpace(tmp[1]) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/process_metrics_other.go b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_other.go index 5e6ac935..4c1c766d 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/process_metrics_other.go +++ b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_other.go @@ -1,4 +1,5 @@ -// +build !linux +//go:build !linux && !windows +// +build !linux,!windows package metrics diff --git a/vendor/github.com/VictoriaMetrics/metrics/process_metrics_windows.go b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_windows.go new file mode 100644 index 00000000..bda7c82f --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/process_metrics_windows.go @@ -0,0 +1,84 @@ +//go:build windows +// +build windows + +package metrics + +import ( + "io" + "log" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + modpsapi = syscall.NewLazyDLL("psapi.dll") + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + + // https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getprocessmemoryinfo + procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") + procGetProcessHandleCount = modkernel32.NewProc("GetProcessHandleCount") +) + +// https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex +type processMemoryCounters struct { + _ uint32 + PageFaultCount uint32 + PeakWorkingSetSize uintptr + WorkingSetSize uintptr + QuotaPeakPagedPoolUsage uintptr + QuotaPagedPoolUsage uintptr + QuotaPeakNonPagedPoolUsage uintptr + QuotaNonPagedPoolUsage uintptr + PagefileUsage uintptr + PeakPagefileUsage uintptr + PrivateUsage uintptr +} + +func writeProcessMetrics(w io.Writer) { + h := windows.CurrentProcess() + var startTime, exitTime, stime, utime windows.Filetime + err := windows.GetProcessTimes(h, &startTime, &exitTime, &stime, &utime) + if err != nil { + log.Printf("ERROR: metrics: cannot read process times: %s", err) + return + } + var mc processMemoryCounters + r1, _, err := procGetProcessMemoryInfo.Call( + uintptr(h), + uintptr(unsafe.Pointer(&mc)), + unsafe.Sizeof(mc), + ) + if r1 != 1 { + log.Printf("ERROR: metrics: cannot read process memory information: %s", err) + return + } + stimeSeconds := float64(uint64(stime.HighDateTime)<<32+uint64(stime.LowDateTime)) / 1e7 + utimeSeconds := float64(uint64(utime.HighDateTime)<<32+uint64(utime.LowDateTime)) / 1e7 + WriteCounterFloat64(w, "process_cpu_seconds_system_total", stimeSeconds) + WriteCounterFloat64(w, "process_cpu_seconds_total", stimeSeconds+utimeSeconds) + WriteCounterFloat64(w, "process_cpu_seconds_user_total", stimeSeconds) + WriteCounterUint64(w, "process_pagefaults_total", uint64(mc.PageFaultCount)) + WriteGaugeUint64(w, "process_start_time_seconds", uint64(startTime.Nanoseconds())/1e9) + WriteGaugeUint64(w, "process_virtual_memory_bytes", uint64(mc.PrivateUsage)) + WriteGaugeUint64(w, "process_resident_memory_peak_bytes", uint64(mc.PeakWorkingSetSize)) + WriteGaugeUint64(w, "process_resident_memory_bytes", uint64(mc.WorkingSetSize)) +} + +func writeFDMetrics(w io.Writer) { + h := windows.CurrentProcess() + var count uint32 + r1, _, err := procGetProcessHandleCount.Call( + uintptr(h), + uintptr(unsafe.Pointer(&count)), + ) + if r1 != 1 { + log.Printf("ERROR: metrics: cannot determine open file descriptors count: %s", err) + return + } + // it seems to be hard-coded limit for 64-bit systems + // https://learn.microsoft.com/en-us/archive/blogs/markrussinovich/pushing-the-limits-of-windows-handles#maximum-number-of-handles + WriteGaugeUint64(w, "process_max_fds", 16777216) + WriteGaugeUint64(w, "process_open_fds", uint64(count)) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram.go b/vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram.go new file mode 100644 index 00000000..ee780220 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram.go @@ -0,0 +1,273 @@ +package metrics + +import ( + "fmt" + "io" + "math" + "sync" + "time" +) + +// PrometheusHistogramDefaultBuckets is a list of the default bucket upper +// bounds. Those default buckets are quite generic, and it is recommended to +// pick custom buckets for improved accuracy. +var PrometheusHistogramDefaultBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10} + +// PrometheusHistogram is a histogram for non-negative values with pre-defined buckets +// +// Each bucket contains a counter for values in the given range. +// Each bucket is exposed via the following metric: +// +// _bucket{,le="upper_bound"} +// +// Where: +// +// - is the metric name passed to NewPrometheusHistogram +// - is optional tags for the , which are passed to NewPrometheusHistogram +// - - upper bound of the current bucket. all samples <= upper_bound are in that bucket +// - - the number of hits to the given bucket during Update* calls +// +// Next to the bucket metrics, two additional metrics track the total number of +// samples (_count) and the total sum (_sum) of all samples: +// +// - _sum{} +// - _count{} +type PrometheusHistogram struct { + // mu guarantees synchronous update for all the counters. + // + // Do not use sync.RWMutex, since it has zero sense from performance PoV. + // It only complicates the code. + mu sync.Mutex + + // upperBounds and buckets are aligned by element position: + // upperBounds[i] defines the upper bound for buckets[i]. + // buckets[i] contains the count of elements <= upperBounds[i] + upperBounds []float64 + buckets []uint64 + + // count is the counter for all observations on this histogram + count uint64 + + // sum is the sum of all the values put into Histogram + sum float64 +} + +// Reset resets previous observations in h. +func (h *PrometheusHistogram) Reset() { + h.mu.Lock() + for i := range h.buckets { + h.buckets[i] = 0 + } + h.sum = 0 + h.count = 0 + h.mu.Unlock() +} + +// Update updates h with v. +// +// Negative values and NaNs are ignored. +func (h *PrometheusHistogram) Update(v float64) { + if math.IsNaN(v) || v < 0 { + // Skip NaNs and negative values. + return + } + bucketIdx := -1 + for i, ub := range h.upperBounds { + if v <= ub { + bucketIdx = i + break + } + } + h.mu.Lock() + h.sum += v + h.count++ + if bucketIdx == -1 { + // +Inf, nothing to do, already accounted for in the total sum + h.mu.Unlock() + return + } + h.buckets[bucketIdx]++ + h.mu.Unlock() +} + +// UpdateDuration updates request duration based on the given startTime. +func (h *PrometheusHistogram) UpdateDuration(startTime time.Time) { + d := time.Since(startTime).Seconds() + h.Update(d) +} + +// NewPrometheusHistogram creates and returns new PrometheusHistogram with the given name +// and PrometheusHistogramDefaultBuckets. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +func NewPrometheusHistogram(name string) *PrometheusHistogram { + return defaultSet.NewPrometheusHistogram(name) +} + +// NewPrometheusHistogramExt creates and returns new PrometheusHistogram with the given name +// and given upperBounds. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +func NewPrometheusHistogramExt(name string, upperBounds []float64) *PrometheusHistogram { + return defaultSet.NewPrometheusHistogramExt(name, upperBounds) +} + +// GetOrCreatePrometheusHistogram returns registered PrometheusHistogram with the given name +// or creates a new PrometheusHistogram if the registry doesn't contain histogram with +// the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewPrometheusHistogram instead of GetOrCreatePrometheusHistogram. +func GetOrCreatePrometheusHistogram(name string) *PrometheusHistogram { + return defaultSet.GetOrCreatePrometheusHistogram(name) +} + +// GetOrCreatePrometheusHistogramExt returns registered PrometheusHistogram with the given name and +// upperBounds or creates new PrometheusHistogram if the registry doesn't contain histogram +// with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewPrometheusHistogramExt instead of GetOrCreatePrometheusHistogramExt. +func GetOrCreatePrometheusHistogramExt(name string, upperBounds []float64) *PrometheusHistogram { + return defaultSet.GetOrCreatePrometheusHistogramExt(name, upperBounds) +} + +func newPrometheusHistogram(upperBounds []float64) *PrometheusHistogram { + mustValidateBuckets(upperBounds) + last := len(upperBounds) - 1 + if math.IsInf(upperBounds[last], +1) { + upperBounds = upperBounds[:last] // ignore +Inf bucket as it is covered anyways + } + h := PrometheusHistogram{ + upperBounds: upperBounds, + buckets: make([]uint64, len(upperBounds)), + } + + return &h +} + +func mustValidateBuckets(upperBounds []float64) { + if err := ValidateBuckets(upperBounds); err != nil { + panic(err) + } +} + +// ValidateBuckets validates the given upperBounds and returns an error +// if validation failed. +func ValidateBuckets(upperBounds []float64) error { + if len(upperBounds) == 0 { + return fmt.Errorf("upperBounds can't be empty") + } + for i := 0; i < len(upperBounds)-1; i++ { + if upperBounds[i] >= upperBounds[i+1] { + return fmt.Errorf("upper bounds for the buckets must be strictly increasing") + } + } + return nil +} + +// LinearBuckets returns a list of upperBounds for PrometheusHistogram, +// and whose distribution is as follows: +// +// [start, start + width, start + 2 * width, ... start + (count-1) * width] +// +// Panics if given start, width and count produce negative buckets or none buckets at all. +func LinearBuckets(start, width float64, count int) []float64 { + if count < 1 { + panic("LinearBuckets: count can't be less than 1") + } + upperBounds := make([]float64, count) + for i := range upperBounds { + upperBounds[i] = start + start += width + } + mustValidateBuckets(upperBounds) + return upperBounds +} + +// ExponentialBuckets returns a list of upperBounds for PrometheusHistogram, +// and whose distribution is as follows: +// +// [start, start * factor pow 1, start * factor pow 2, ... start * factor pow (count-1)] +// +// Panics if given start, width and count produce negative buckets or none buckets at all. +func ExponentialBuckets(start, factor float64, count int) []float64 { + if count < 1 { + panic("ExponentialBuckets: count can't be less than 1") + } + if factor <= 1 { + panic("ExponentialBuckets: factor must be greater than 1") + } + if start <= 0 { + panic("ExponentialBuckets: start can't be less than 0") + } + upperBounds := make([]float64, count) + for i := range upperBounds { + upperBounds[i] = start + start *= factor + } + mustValidateBuckets(upperBounds) + return upperBounds +} + +func (h *PrometheusHistogram) marshalTo(prefix string, w io.Writer) { + cumulativeSum := uint64(0) + h.mu.Lock() + count := h.count + sum := h.sum + for i, ub := range h.upperBounds { + cumulativeSum += h.buckets[i] + tag := fmt.Sprintf(`le="%v"`, ub) + metricName := addTag(prefix, tag) + name, labels := splitMetricName(metricName) + fmt.Fprintf(w, "%s_bucket%s %d\n", name, labels, cumulativeSum) + } + h.mu.Unlock() + + tag := fmt.Sprintf("le=%q", "+Inf") + metricName := addTag(prefix, tag) + name, labels := splitMetricName(metricName) + fmt.Fprintf(w, "%s_bucket%s %d\n", name, labels, count) + + name, labels = splitMetricName(prefix) + if float64(int64(sum)) == sum { + fmt.Fprintf(w, "%s_sum%s %d\n", name, labels, int64(sum)) + } else { + fmt.Fprintf(w, "%s_sum%s %g\n", name, labels, sum) + } + fmt.Fprintf(w, "%s_count%s %d\n", name, labels, count) +} + +func (h *PrometheusHistogram) metricType() string { + return "histogram" +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram_example_test.go b/vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram_example_test.go new file mode 100644 index 00000000..ea343efd --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram_example_test.go @@ -0,0 +1,27 @@ +package metrics_test + +import ( + "fmt" + "time" + + "github.com/VictoriaMetrics/metrics" +) + +func ExamplePrometheusHistogram() { + // Define a histogram in global scope. + h := metrics.NewPrometheusHistogram(`request_duration_seconds{path="/foo/bar"}`) + + // Update the histogram with the duration of processRequest call. + startTime := time.Now() + processRequest() + h.UpdateDuration(startTime) +} + +func ExamplePrometheusHistogram_vec() { + for i := 0; i < 3; i++ { + // Dynamically construct metric name and pass it to GetOrCreatePrometheusHistogram. + name := fmt.Sprintf(`response_size_bytes{path=%q, code=%q}`, "/foo/bar", 200+i) + response := processRequest() + metrics.GetOrCreatePrometheusHistogram(name).Update(float64(len(response))) + } +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram_test.go b/vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram_test.go new file mode 100644 index 00000000..f66ec138 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/prometheus_histogram_test.go @@ -0,0 +1,194 @@ +package metrics + +import ( + "bytes" + "fmt" + "math" + "reflect" + "strings" + "testing" +) + +func TestPrometheusHistogramEmpty(t *testing.T) { + const expected string = `empty_bucket{le="1"} 0 +empty_bucket{le="2"} 0 +empty_bucket{le="4"} 0 +empty_bucket{le="+Inf"} 0 +empty_sum 0 +empty_count 0 +` + // histogram without registered observations should be still rendered + h := NewPrometheusHistogramExt("empty", []float64{1, 2, 4}) + testMarshalTo(t, h, "empty", expected) +} + +func TestPrometheusHistogramSerial(t *testing.T) { + const expected string = `prefix_bucket{le="0.005"} 1 +prefix_bucket{le="0.01"} 1 +prefix_bucket{le="0.025"} 2 +prefix_bucket{le="0.05"} 3 +prefix_bucket{le="0.1"} 5 +prefix_bucket{le="0.25"} 11 +prefix_bucket{le="0.5"} 21 +prefix_bucket{le="1"} 41 +prefix_bucket{le="2.5"} 101 +prefix_bucket{le="5"} 201 +prefix_bucket{le="10"} 401 +prefix_bucket{le="+Inf"} 405 +prefix_sum 2045.25 +prefix_count 405 +` + name := "TestPrometheusHistogramSerial" + h := NewPrometheusHistogram(name) + + // Update histogram + for i := 0; i <= 10_100; i += 25 { // from 0 to 10'100 ms in 25ms steps + h.Update(float64(i) * 1e-3) + } + + // Make sure the histogram prints _bucket on marshalTo call + testMarshalTo(t, h, "prefix", expected) + + // make sure that if the +Inf bucket is manually specified it gets ignored and we have the same results at the end + h2 := NewPrometheusHistogramExt("TestPrometheusHistogram2", append(PrometheusHistogramDefaultBuckets, math.Inf(+1))) + + // reset h to validate that it works + h.Reset() + + // Update both histograms with valid values + for i := 0; i <= 10_100; i += 25 { // from 0 to 10'100 ms in 25ms steps + h.Update(float64(i) * 1e-3) + h2.Update(float64(i) * 1e-3) + } + + // update with negative and NaN values, those must be ignored + h.Update(-1) + h2.Update(math.NaN()) + + // Make sure the histogram prints _bucket on marshalTo call + testMarshalTo(t, h, "prefix", expected) + testMarshalTo(t, h2, "prefix", expected) +} + +func TestPrometheusHistogramLinearBuckets(t *testing.T) { + f := func(start, width float64, count int, exp []float64) { + t.Helper() + got := LinearBuckets(start, width, count) + if !reflect.DeepEqual(got, exp) { + t.Fatalf("expected to get: \n%v\ngot:\n%v", exp, got) + } + } + f(0.5, 1.0, 4, []float64{0.5, 1.5, 2.5, 3.5}) + f(1, 4, 4, []float64{1, 5, 9, 13}) + f(-5, 5, 3, []float64{-5, 0, 5}) + + fRecover := func(start, width float64, count int) { + t.Helper() + defer func() { + if r := recover(); r == nil { + t.Errorf("test expected to fail with panic") + } + }() + f(start, width, count, nil) + } + // make sure we panic for bad input + fRecover(0.5, 1.0, 0) + fRecover(0.5, -1, 2) +} + +func TestPrometheusHistogramExponentialBuckets(t *testing.T) { + f := func(start, factor float64, count int, exp []float64) { + t.Helper() + got := ExponentialBuckets(start, factor, count) + if !reflect.DeepEqual(got, exp) { + t.Fatalf("expected to get: \n%v\ngot:\n%v", exp, got) + } + } + f(1, 2, 4, []float64{1, 2, 4, 8}) + f(0.5, 4, 4, []float64{0.5, 2, 8, 32}) + + fRecover := func(start, factor float64, count int) { + t.Helper() + defer func() { + if r := recover(); r == nil { + t.Errorf("test expected to fail with panic") + } + }() + f(start, factor, count, nil) + } + // make sure we panic for bad input + fRecover(1, 2, 0) + fRecover(1, 1, 1) + fRecover(0, 2, 1) +} + +// inspired from https://github.com/prometheus/client_golang/blob/main/prometheus/histogram_test.go +func TestPrometheusHistogramNonMonotonicBuckets(t *testing.T) { + i := 0 + f := func(name string, upperBounds []float64) { + t.Helper() + defer func() { + if r := recover(); r == nil { + t.Errorf("Buckets %v are %s but NewPrometheusHistogram did not panic.", upperBounds, name) + } + }() + _ = NewPrometheusHistogramExt(fmt.Sprintf("test_%d", i), upperBounds) + } + + f("0 bucket is invalid", []float64{}) + f("not strictly monotonic", []float64{1, 2, 2, 3}) + f("not monotonic at all", []float64{1, 2, 4, 3, 5}) + f("have +Inf in the middle", []float64{1, 2, math.Inf(+1), 3}) +} + +func TestPrometheusHistogramWithTags(t *testing.T) { + f := func(expOutput string) { + t.Helper() + var bb bytes.Buffer + WritePrometheus(&bb, false) + result := bb.String() + if !strings.Contains(result, expOutput) { + t.Fatalf("missing histogram %s in the WritePrometheus output; got\n%s", expOutput, result) + } + } + + h := NewPrometheusHistogram(`TestPrometheusHistogram`) + h.Update(123) + f(`TestPrometheusHistogram_bucket{le="+Inf"} 1`) + f(`TestPrometheusHistogram_count 1`) + f(`TestPrometheusHistogram_sum 123`) + + h = NewPrometheusHistogram(`TestPrometheusHistogram{tag="foo"}`) + h.Update(123) + f(`TestPrometheusHistogram_bucket{tag="foo",le="+Inf"} 1`) + f(`TestPrometheusHistogram_count{tag="foo"} 1`) + f(`TestPrometheusHistogram_sum{tag="foo"} 123`) +} + +func TestGetOrCreatePrometheusHistogramSerial(t *testing.T) { + name := "GetOrCreatePrometheusHistogramSerial" + if err := testGetOrCreatePrometheusHistogram(name); err != nil { + t.Fatal(err) + } +} + +func TestGetOrCreatePrometheusHistogramConcurrent(t *testing.T) { + name := "GetOrCreatePrometheusHistogramConcurrent" + err := testConcurrent(func() error { + return testGetOrCreatePrometheusHistogram(name) + }) + if err != nil { + t.Fatal(err) + } +} + +func testGetOrCreatePrometheusHistogram(name string) error { + h1 := GetOrCreatePrometheusHistogram(name) + for i := 0; i < 10; i++ { + h2 := GetOrCreatePrometheusHistogram(name) + if h1 != h2 { + return fmt.Errorf("unexpected histogram returned; got %p; want %p", h2, h1) + } + } + return nil +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/push.go b/vendor/github.com/VictoriaMetrics/metrics/push.go new file mode 100644 index 00000000..f520fe38 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/push.go @@ -0,0 +1,510 @@ +package metrics + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "strings" + "sync" + "time" + + "compress/gzip" +) + +// PushOptions is the list of options, which may be applied to InitPushWithOptions(). +type PushOptions struct { + // ExtraLabels is an optional comma-separated list of `label="value"` labels, which must be added to all the metrics before pushing them to pushURL. + ExtraLabels string + + // Headers is an optional list of HTTP headers to add to every push request to pushURL. + // + // Every item in the list must have the form `Header: value`. For example, `Authorization: Custom my-top-secret`. + Headers []string + + // Whether to disable HTTP request body compression before sending the metrics to pushURL. + // + // By default the compression is enabled. + DisableCompression bool + + // Method is HTTP request method to use when pushing metrics to pushURL. + // + // By default the Method is GET. + Method string + + // Optional WaitGroup for waiting until all the push workers created with this WaitGroup are stopped. + WaitGroup *sync.WaitGroup +} + +// InitPushWithOptions sets up periodic push for globally registered metrics to the given pushURL with the given interval. +// +// The periodic push is stopped when ctx is canceled. +// It is possible to wait until the background metrics push worker is stopped on a WaitGroup passed via opts.WaitGroup. +// +// If pushProcessMetrics is set to true, then 'process_*' and `go_*` metrics are also pushed to pushURL. +// +// opts may contain additional configuration options if non-nil. +// +// The metrics are pushed to pushURL in Prometheus text exposition format. +// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format +// +// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to +// https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format +// +// It is OK calling InitPushWithOptions multiple times with different pushURL - +// in this case metrics are pushed to all the provided pushURL urls. +func InitPushWithOptions(ctx context.Context, pushURL string, interval time.Duration, pushProcessMetrics bool, opts *PushOptions) error { + writeMetrics := func(w io.Writer) { + WritePrometheus(w, pushProcessMetrics) + } + return InitPushExtWithOptions(ctx, pushURL, interval, writeMetrics, opts) +} + +// InitPushProcessMetrics sets up periodic push for 'process_*' metrics to the given pushURL with the given interval. +// +// extraLabels may contain comma-separated list of `label="value"` labels, which will be added +// to all the metrics before pushing them to pushURL. +// +// The metrics are pushed to pushURL in Prometheus text exposition format. +// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format +// +// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to +// https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format +// +// It is OK calling InitPushProcessMetrics multiple times with different pushURL - +// in this case metrics are pushed to all the provided pushURL urls. +func InitPushProcessMetrics(pushURL string, interval time.Duration, extraLabels string) error { + return InitPushExt(pushURL, interval, extraLabels, WriteProcessMetrics) +} + +// InitPush sets up periodic push for globally registered metrics to the given pushURL with the given interval. +// +// extraLabels may contain comma-separated list of `label="value"` labels, which will be added +// to all the metrics before pushing them to pushURL. +// +// If pushProcessMetrics is set to true, then 'process_*' and `go_*` metrics are also pushed to pushURL. +// +// The metrics are pushed to pushURL in Prometheus text exposition format. +// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format +// +// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to +// https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format +// +// It is OK calling InitPush multiple times with different pushURL - +// in this case metrics are pushed to all the provided pushURL urls. +func InitPush(pushURL string, interval time.Duration, extraLabels string, pushProcessMetrics bool) error { + writeMetrics := func(w io.Writer) { + WritePrometheus(w, pushProcessMetrics) + } + return InitPushExt(pushURL, interval, extraLabels, writeMetrics) +} + +// PushMetrics pushes globally registered metrics to pushURL. +// +// If pushProcessMetrics is set to true, then 'process_*' and `go_*` metrics are also pushed to pushURL. +// +// opts may contain additional configuration options if non-nil. +// +// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to +// https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format +func PushMetrics(ctx context.Context, pushURL string, pushProcessMetrics bool, opts *PushOptions) error { + writeMetrics := func(w io.Writer) { + WritePrometheus(w, pushProcessMetrics) + } + return PushMetricsExt(ctx, pushURL, writeMetrics, opts) +} + +// InitPushWithOptions sets up periodic push for metrics from s to the given pushURL with the given interval. +// +// The periodic push is stopped when the ctx is canceled. +// It is possible to wait until the background metrics push worker is stopped on a WaitGroup passed via opts.WaitGroup. +// +// opts may contain additional configuration options if non-nil. +// +// The metrics are pushed to pushURL in Prometheus text exposition format. +// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format +// +// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to +// https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format +// +// It is OK calling InitPushWithOptions multiple times with different pushURL - +// in this case metrics are pushed to all the provided pushURL urls. +func (s *Set) InitPushWithOptions(ctx context.Context, pushURL string, interval time.Duration, opts *PushOptions) error { + return InitPushExtWithOptions(ctx, pushURL, interval, s.WritePrometheus, opts) +} + +// InitPush sets up periodic push for metrics from s to the given pushURL with the given interval. +// +// extraLabels may contain comma-separated list of `label="value"` labels, which will be added +// to all the metrics before pushing them to pushURL. +// +// The metrics are pushed to pushURL in Prometheus text exposition format. +// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format +// +// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to +// https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format +// +// It is OK calling InitPush multiple times with different pushURL - +// in this case metrics are pushed to all the provided pushURL urls. +func (s *Set) InitPush(pushURL string, interval time.Duration, extraLabels string) error { + return InitPushExt(pushURL, interval, extraLabels, s.WritePrometheus) +} + +// PushMetrics pushes s metrics to pushURL. +// +// opts may contain additional configuration options if non-nil. +// +// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to +// https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format +func (s *Set) PushMetrics(ctx context.Context, pushURL string, opts *PushOptions) error { + return PushMetricsExt(ctx, pushURL, s.WritePrometheus, opts) +} + +// InitPushExt sets up periodic push for metrics obtained by calling writeMetrics with the given interval. +// +// extraLabels may contain comma-separated list of `label="value"` labels, which will be added +// to all the metrics before pushing them to pushURL. +// +// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments. +// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format +// +// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to +// https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format +// +// It is OK calling InitPushExt multiple times with different pushURL - +// in this case metrics are pushed to all the provided pushURL urls. +// +// It is OK calling InitPushExt multiple times with different writeMetrics - +// in this case all the metrics generated by writeMetrics callbacks are written to pushURL. +func InitPushExt(pushURL string, interval time.Duration, extraLabels string, writeMetrics func(w io.Writer)) error { + opts := &PushOptions{ + ExtraLabels: extraLabels, + } + return InitPushExtWithOptions(context.Background(), pushURL, interval, writeMetrics, opts) +} + +// InitPushExtWithOptions sets up periodic push for metrics obtained by calling writeMetrics with the given interval. +// +// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments. +// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format +// +// The periodic push is stopped when the ctx is canceled. +// It is possible to wait until the background metrics push worker is stopped on a WaitGroup passed via opts.WaitGroup. +// +// opts may contain additional configuration options if non-nil. +// +// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to +// https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format +// +// It is OK calling InitPushExtWithOptions multiple times with different pushURL - +// in this case metrics are pushed to all the provided pushURL urls. +// +// It is OK calling InitPushExtWithOptions multiple times with different writeMetrics - +// in this case all the metrics generated by writeMetrics callbacks are written to pushURL. +func InitPushExtWithOptions(ctx context.Context, pushURL string, interval time.Duration, writeMetrics func(w io.Writer), opts *PushOptions) error { + pc, err := newPushContext(pushURL, opts) + if err != nil { + return err + } + + // validate interval + if interval <= 0 { + return fmt.Errorf("interval must be positive; got %s", interval) + } + pushMetricsSet.GetOrCreateFloatCounter(fmt.Sprintf(`metrics_push_interval_seconds{url=%q}`, pc.pushURLRedacted)).Set(interval.Seconds()) + + var wg *sync.WaitGroup + if opts != nil { + wg = opts.WaitGroup + if wg != nil { + wg.Add(1) + } + } + go func() { + ticker := time.NewTicker(interval) + defer ticker.Stop() + stopCh := ctx.Done() + for { + select { + case <-ticker.C: + ctxLocal, cancel := context.WithTimeout(ctx, interval+time.Second) + err := pc.pushMetrics(ctxLocal, writeMetrics) + cancel() + if err != nil { + log.Printf("ERROR: metrics.push: %s", err) + } + case <-stopCh: + if wg != nil { + wg.Done() + } + return + } + } + }() + + return nil +} + +// PushMetricsExt pushes metrics generated by wirteMetrics to pushURL. +// +// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments. +// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format +// +// opts may contain additional configuration options if non-nil. +// +// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to +// https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-import-data-in-prometheus-exposition-format +func PushMetricsExt(ctx context.Context, pushURL string, writeMetrics func(w io.Writer), opts *PushOptions) error { + pc, err := newPushContext(pushURL, opts) + if err != nil { + return err + } + return pc.pushMetrics(ctx, writeMetrics) +} + +type pushContext struct { + pushURL *url.URL + method string + pushURLRedacted string + extraLabels string + headers http.Header + disableCompression bool + + client *http.Client + + pushesTotal *Counter + bytesPushedTotal *Counter + pushBlockSize *Histogram + pushDuration *Histogram + pushErrors *Counter +} + +func newPushContext(pushURL string, opts *PushOptions) (*pushContext, error) { + if opts == nil { + opts = &PushOptions{} + } + + // validate pushURL + pu, err := url.Parse(pushURL) + if err != nil { + return nil, fmt.Errorf("cannot parse pushURL=%q: %w", pushURL, err) + } + if pu.Scheme != "http" && pu.Scheme != "https" { + return nil, fmt.Errorf("unsupported scheme in pushURL=%q; expecting 'http' or 'https'", pushURL) + } + if pu.Host == "" { + return nil, fmt.Errorf("missing host in pushURL=%q", pushURL) + } + + method := opts.Method + if method == "" { + method = http.MethodGet + } + + // validate ExtraLabels + extraLabels := opts.ExtraLabels + if err := validateTags(extraLabels); err != nil { + return nil, fmt.Errorf("invalid extraLabels=%q: %w", extraLabels, err) + } + + // validate Headers + headers := make(http.Header) + for _, h := range opts.Headers { + n := strings.IndexByte(h, ':') + if n < 0 { + return nil, fmt.Errorf("missing `:` delimiter in the header %q", h) + } + name := strings.TrimSpace(h[:n]) + value := strings.TrimSpace(h[n+1:]) + headers.Add(name, value) + } + + pushURLRedacted := pu.Redacted() + client := &http.Client{} + return &pushContext{ + pushURL: pu, + method: method, + pushURLRedacted: pushURLRedacted, + extraLabels: extraLabels, + headers: headers, + disableCompression: opts.DisableCompression, + + client: client, + + pushesTotal: pushMetricsSet.GetOrCreateCounter(fmt.Sprintf(`metrics_push_total{url=%q}`, pushURLRedacted)), + bytesPushedTotal: pushMetricsSet.GetOrCreateCounter(fmt.Sprintf(`metrics_push_bytes_pushed_total{url=%q}`, pushURLRedacted)), + pushBlockSize: pushMetricsSet.GetOrCreateHistogram(fmt.Sprintf(`metrics_push_block_size_bytes{url=%q}`, pushURLRedacted)), + pushDuration: pushMetricsSet.GetOrCreateHistogram(fmt.Sprintf(`metrics_push_duration_seconds{url=%q}`, pushURLRedacted)), + pushErrors: pushMetricsSet.GetOrCreateCounter(fmt.Sprintf(`metrics_push_errors_total{url=%q}`, pushURLRedacted)), + }, nil +} + +func (pc *pushContext) pushMetrics(ctx context.Context, writeMetrics func(w io.Writer)) error { + bb := getBytesBuffer() + defer putBytesBuffer(bb) + + writeMetrics(bb) + + if len(pc.extraLabels) > 0 { + bbTmp := getBytesBuffer() + bbTmp.B = append(bbTmp.B[:0], bb.B...) + bb.B = addExtraLabels(bb.B[:0], bbTmp.B, pc.extraLabels) + putBytesBuffer(bbTmp) + } + if !pc.disableCompression { + bbTmp := getBytesBuffer() + bbTmp.B = append(bbTmp.B[:0], bb.B...) + bb.B = bb.B[:0] + zw := getGzipWriter(bb) + if _, err := zw.Write(bbTmp.B); err != nil { + panic(fmt.Errorf("BUG: cannot write %d bytes to gzip writer: %s", len(bbTmp.B), err)) + } + if err := zw.Close(); err != nil { + panic(fmt.Errorf("BUG: cannot flush metrics to gzip writer: %s", err)) + } + putGzipWriter(zw) + putBytesBuffer(bbTmp) + } + + // Update metrics + pc.pushesTotal.Inc() + blockLen := len(bb.B) + pc.bytesPushedTotal.Add(blockLen) + pc.pushBlockSize.Update(float64(blockLen)) + + // Prepare the request to sent to pc.pushURL + reqBody := bytes.NewReader(bb.B) + req, err := http.NewRequestWithContext(ctx, pc.method, pc.pushURL.String(), reqBody) + if err != nil { + panic(fmt.Errorf("BUG: metrics.push: cannot initialize request for metrics push to %q: %w", pc.pushURLRedacted, err)) + } + + req.Header.Set("Content-Type", "text/plain") + // Set the needed headers, and `Content-Type` allowed be overwrited. + for name, values := range pc.headers { + for _, value := range values { + req.Header.Add(name, value) + } + } + if !pc.disableCompression { + req.Header.Set("Content-Encoding", "gzip") + } + + // Perform the request + startTime := time.Now() + resp, err := pc.client.Do(req) + pc.pushDuration.UpdateDuration(startTime) + if err != nil { + if errors.Is(err, context.Canceled) { + return nil + } + pc.pushErrors.Inc() + return fmt.Errorf("cannot push metrics to %q: %s", pc.pushURLRedacted, err) + } + if resp.StatusCode/100 != 2 { + body, _ := ioutil.ReadAll(resp.Body) + _ = resp.Body.Close() + pc.pushErrors.Inc() + return fmt.Errorf("unexpected status code in response from %q: %d; expecting 2xx; response body: %q", pc.pushURLRedacted, resp.StatusCode, body) + } + _ = resp.Body.Close() + return nil +} + +var pushMetricsSet = NewSet() + +func writePushMetrics(w io.Writer) { + pushMetricsSet.WritePrometheus(w) +} + +func addExtraLabels(dst, src []byte, extraLabels string) []byte { + for len(src) > 0 { + var line []byte + n := bytes.IndexByte(src, '\n') + if n >= 0 { + line = src[:n] + src = src[n+1:] + } else { + line = src + src = nil + } + line = bytes.TrimSpace(line) + if len(line) == 0 { + // Skip empy lines + continue + } + if bytes.HasPrefix(line, bashBytes) { + // Copy comments as is + dst = append(dst, line...) + dst = append(dst, '\n') + continue + } + n = bytes.IndexByte(line, '{') + if n >= 0 { + dst = append(dst, line[:n+1]...) + dst = append(dst, extraLabels...) + dst = append(dst, ',') + dst = append(dst, line[n+1:]...) + } else { + n = bytes.LastIndexByte(line, ' ') + if n < 0 { + panic(fmt.Errorf("BUG: missing whitespace between metric name and metric value in Prometheus text exposition line %q", line)) + } + dst = append(dst, line[:n]...) + dst = append(dst, '{') + dst = append(dst, extraLabels...) + dst = append(dst, '}') + dst = append(dst, line[n:]...) + } + dst = append(dst, '\n') + } + return dst +} + +var bashBytes = []byte("#") + +func getBytesBuffer() *bytesBuffer { + v := bytesBufferPool.Get() + if v == nil { + return &bytesBuffer{} + } + return v.(*bytesBuffer) +} + +func putBytesBuffer(bb *bytesBuffer) { + bb.B = bb.B[:0] + bytesBufferPool.Put(bb) +} + +var bytesBufferPool sync.Pool + +type bytesBuffer struct { + B []byte +} + +func (bb *bytesBuffer) Write(p []byte) (int, error) { + bb.B = append(bb.B, p...) + return len(p), nil +} + +func getGzipWriter(w io.Writer) *gzip.Writer { + v := gzipWriterPool.Get() + if v == nil { + return gzip.NewWriter(w) + } + zw := v.(*gzip.Writer) + zw.Reset(w) + return zw +} + +func putGzipWriter(zw *gzip.Writer) { + zw.Reset(io.Discard) + gzipWriterPool.Put(zw) +} + +var gzipWriterPool sync.Pool diff --git a/vendor/github.com/VictoriaMetrics/metrics/push_test.go b/vendor/github.com/VictoriaMetrics/metrics/push_test.go new file mode 100644 index 00000000..45029d89 --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/push_test.go @@ -0,0 +1,243 @@ +package metrics + +import ( + "bytes" + "compress/gzip" + "context" + "io" + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" +) + +func TestAddExtraLabels(t *testing.T) { + f := func(s, extraLabels, expectedResult string) { + t.Helper() + result := addExtraLabels(nil, []byte(s), extraLabels) + if string(result) != expectedResult { + t.Fatalf("unexpected result; got\n%s\nwant\n%s", result, expectedResult) + } + } + f("", `foo="bar"`, "") + f("a 123", `foo="bar"`, `a{foo="bar"} 123`+"\n") + f(`a{b="c"} 1.3`, `foo="bar"`, `a{foo="bar",b="c"} 1.3`+"\n") + f(`a{b="c}{"} 1.3`, `foo="bar",baz="x"`, `a{foo="bar",baz="x",b="c}{"} 1.3`+"\n") + f(`foo 1 +bar{a="x"} 2 +`, `foo="bar"`, `foo{foo="bar"} 1 +bar{foo="bar",a="x"} 2 +`) + f(` +foo 1 +# some counter +# type foobar counter + foobar{a="b",c="d"} 4`, `x="y"`, `foo{x="y"} 1 +# some counter +# type foobar counter +foobar{x="y",a="b",c="d"} 4 +`) +} + +func TestInitPushFailure(t *testing.T) { + f := func(pushURL string, interval time.Duration, extraLabels string) { + t.Helper() + if err := InitPush(pushURL, interval, extraLabels, false); err == nil { + t.Fatalf("expecting non-nil error") + } + } + + // Invalid url + f("foobar", time.Second, "") + f("aaa://foobar", time.Second, "") + f("http:///bar", time.Second, "") + + // Non-positive interval + f("http://foobar", 0, "") + f("http://foobar", -time.Second, "") + + // Invalid extraLabels + f("http://foobar", time.Second, "foo") + f("http://foobar", time.Second, "foo{bar") + f("http://foobar", time.Second, "foo=bar") + f("http://foobar", time.Second, "foo='bar'") + f("http://foobar", time.Second, `foo="bar",baz`) + f("http://foobar", time.Second, `{foo="bar"}`) + f("http://foobar", time.Second, `a{foo="bar"}`) +} + +func TestInitPushWithOptions(t *testing.T) { + f := func(s *Set, opts *PushOptions, expectedHeaders, expectedData string) { + t.Helper() + + var reqHeaders []byte + var reqData []byte + var reqErr error + doneCh := make(chan struct{}) + firstRequest := true + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if firstRequest { + var bb bytes.Buffer + r.Header.WriteSubset(&bb, map[string]bool{ + "Accept-Encoding": true, + "Content-Length": true, + "User-Agent": true, + }) + reqHeaders = bb.Bytes() + reqData, reqErr = io.ReadAll(r.Body) + close(doneCh) + firstRequest = false + } + })) + defer srv.Close() + ctx, cancel := context.WithCancel(context.Background()) + var wg sync.WaitGroup + if opts != nil { + opts.WaitGroup = &wg + } + if err := s.InitPushWithOptions(ctx, srv.URL, time.Millisecond, opts); err != nil { + t.Fatalf("unexpected error: %s", err) + } + select { + case <-time.After(5 * time.Second): + t.Fatalf("timeout!") + case <-doneCh: + // stop the periodic pusher + cancel() + wg.Wait() + } + if reqErr != nil { + t.Fatalf("unexpected error: %s", reqErr) + } + if opts == nil || !opts.DisableCompression { + zr, err := gzip.NewReader(bytes.NewBuffer(reqData)) + if err != nil { + t.Fatalf("cannot initialize gzip reader: %s", err) + } + data, err := io.ReadAll(zr) + if err != nil { + t.Fatalf("cannot read data from gzip reader: %s", err) + } + if err := zr.Close(); err != nil { + t.Fatalf("unexpected error when closing gzip reader: %s", err) + } + reqData = data + } + if string(reqHeaders) != expectedHeaders { + t.Fatalf("unexpected request headers; got\n%s\nwant\n%s", reqHeaders, expectedHeaders) + } + if string(reqData) != expectedData { + t.Fatalf("unexpected data; got\n%s\nwant\n%s", reqData, expectedData) + } + } + + s := NewSet() + c := s.NewCounter("foo") + c.Set(1234) + _ = s.NewGauge("bar", func() float64 { + return 42.12 + }) + + // nil PushOptions + f(s, nil, "Content-Encoding: gzip\r\nContent-Type: text/plain\r\n", "bar 42.12\nfoo 1234\n") + + // Disable compression on the pushed request body + f(s, &PushOptions{ + DisableCompression: true, + }, "Content-Type: text/plain\r\n", "bar 42.12\nfoo 1234\n") + + // Add extra labels + f(s, &PushOptions{ + ExtraLabels: `label1="value1",label2="value2"`, + }, "Content-Encoding: gzip\r\nContent-Type: text/plain\r\n", `bar{label1="value1",label2="value2"} 42.12`+"\n"+`foo{label1="value1",label2="value2"} 1234`+"\n") + + // Add extra headers + f(s, &PushOptions{ + Headers: []string{"Foo: Bar", "baz:aaaa-bbb"}, + }, "Baz: aaaa-bbb\r\nContent-Encoding: gzip\r\nContent-Type: text/plain\r\nFoo: Bar\r\n", "bar 42.12\nfoo 1234\n") +} + +func TestPushMetrics(t *testing.T) { + f := func(s *Set, opts *PushOptions, expectedHeaders, expectedData string) { + t.Helper() + + var reqHeaders []byte + var reqData []byte + var reqErr error + doneCh := make(chan struct{}) + firstRequest := true + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if firstRequest { + var bb bytes.Buffer + r.Header.WriteSubset(&bb, map[string]bool{ + "Accept-Encoding": true, + "Content-Length": true, + "User-Agent": true, + }) + reqHeaders = bb.Bytes() + reqData, reqErr = io.ReadAll(r.Body) + close(doneCh) + firstRequest = false + } + })) + defer srv.Close() + ctx := context.Background() + if err := s.PushMetrics(ctx, srv.URL, opts); err != nil { + t.Fatalf("unexpected error: %s", err) + } + select { + case <-time.After(5 * time.Second): + t.Fatalf("timeout!") + case <-doneCh: + } + if reqErr != nil { + t.Fatalf("unexpected error: %s", reqErr) + } + if opts == nil || !opts.DisableCompression { + zr, err := gzip.NewReader(bytes.NewBuffer(reqData)) + if err != nil { + t.Fatalf("cannot initialize gzip reader: %s", err) + } + data, err := io.ReadAll(zr) + if err != nil { + t.Fatalf("cannot read data from gzip reader: %s", err) + } + if err := zr.Close(); err != nil { + t.Fatalf("unexpected error when closing gzip reader: %s", err) + } + reqData = data + } + if string(reqHeaders) != expectedHeaders { + t.Fatalf("unexpected request headers; got\n%s\nwant\n%s", reqHeaders, expectedHeaders) + } + if string(reqData) != expectedData { + t.Fatalf("unexpected data; got\n%s\nwant\n%s", reqData, expectedData) + } + } + + s := NewSet() + c := s.NewCounter("foo") + c.Set(1234) + _ = s.NewGauge("bar", func() float64 { + return 42.12 + }) + + // nil PushOptions + f(s, nil, "Content-Encoding: gzip\r\nContent-Type: text/plain\r\n", "bar 42.12\nfoo 1234\n") + + // Disable compression on the pushed request body + f(s, &PushOptions{ + DisableCompression: true, + }, "Content-Type: text/plain\r\n", "bar 42.12\nfoo 1234\n") + + // Add extra labels + f(s, &PushOptions{ + ExtraLabels: `label1="value1",label2="value2"`, + }, "Content-Encoding: gzip\r\nContent-Type: text/plain\r\n", `bar{label1="value1",label2="value2"} 42.12`+"\n"+`foo{label1="value1",label2="value2"} 1234`+"\n") + + // Add extra headers + f(s, &PushOptions{ + Headers: []string{"Foo: Bar", "baz:aaaa-bbb"}, + }, "Baz: aaaa-bbb\r\nContent-Encoding: gzip\r\nContent-Type: text/plain\r\nFoo: Bar\r\n", "bar 42.12\nfoo 1234\n") +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/push_timing_test.go b/vendor/github.com/VictoriaMetrics/metrics/push_timing_test.go new file mode 100644 index 00000000..8316143d --- /dev/null +++ b/vendor/github.com/VictoriaMetrics/metrics/push_timing_test.go @@ -0,0 +1,20 @@ +package metrics + +import ( + "testing" +) + +func BenchmarkAddExtraLabels(b *testing.B) { + extraLabels := `foo="bar"` + src := []byte(`foo 1 +bar{baz="x"} 2 +`) + b.ReportAllocs() + b.SetBytes(1) + b.RunParallel(func(pb *testing.PB) { + var dst []byte + for pb.Next() { + dst = addExtraLabels(dst[:0], src, extraLabels) + } + }) +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/set.go b/vendor/github.com/VictoriaMetrics/metrics/set.go index 69b4de86..31f9f69c 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/set.go +++ b/vendor/github.com/VictoriaMetrics/metrics/set.go @@ -19,9 +19,13 @@ type Set struct { a []*namedMetric m map[string]*namedMetric summaries []*Summary + + metricsWriters []func(w io.Writer) } // NewSet creates new set of metrics. +// +// Pass the set to RegisterSet() function in order to export its metrics via global WritePrometheus() call. func NewSet() *Set { return &Set{ m: make(map[string]*namedMetric), @@ -43,14 +47,27 @@ func (s *Set) WritePrometheus(w io.Writer) { sort.Slice(s.a, lessFunc) } sa := append([]*namedMetric(nil), s.a...) + metricsWriters := s.metricsWriters s.mu.Unlock() - // Call marshalTo without the global lock, since certain metric types such as Gauge - // can call a callback, which, in turn, can try calling s.mu.Lock again. + prevMetricFamily := "" for _, nm := range sa { + metricFamily := getMetricFamily(nm.name) + if metricFamily != prevMetricFamily { + // write meta info only once per metric family + metricType := nm.metric.metricType() + WriteMetadataIfNeeded(&bb, nm.name, metricType) + prevMetricFamily = metricFamily + } + // Call marshalTo without the global lock, since certain metric types such as Gauge + // can call a callback, which, in turn, can try calling s.mu.Lock again. nm.metric.marshalTo(nm.name, &bb) } w.Write(bb.Bytes()) + + for _, writeMetrics := range metricsWriters { + writeMetrics(w) + } } // NewHistogram creates and returns new histogram in s with the given name. @@ -58,9 +75,9 @@ func (s *Set) WritePrometheus(w io.Writer) { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned histogram is safe to use from concurrent goroutines. func (s *Set) NewHistogram(name string) *Histogram { @@ -75,9 +92,9 @@ func (s *Set) NewHistogram(name string) *Histogram { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned histogram is safe to use from concurrent goroutines. // @@ -88,7 +105,7 @@ func (s *Set) GetOrCreateHistogram(name string) *Histogram { s.mu.Unlock() if nm == nil { // Slow path - create and register missing histogram. - if err := validateMetric(name); err != nil { + if err := ValidateMetric(name); err != nil { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } nmNew := &namedMetric{ @@ -111,14 +128,107 @@ func (s *Set) GetOrCreateHistogram(name string) *Histogram { return h } +// NewPrometheusHistogram creates and returns new PrometheusHistogram in s +// with the given name and PrometheusHistogramDefaultBuckets. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +func (s *Set) NewPrometheusHistogram(name string) *PrometheusHistogram { + return s.NewPrometheusHistogramExt(name, PrometheusHistogramDefaultBuckets) +} + +// NewPrometheusHistogramExt creates and returns new PrometheusHistogram in s +// with the given name and upperBounds. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +func (s *Set) NewPrometheusHistogramExt(name string, upperBounds []float64) *PrometheusHistogram { + h := newPrometheusHistogram(upperBounds) + s.registerMetric(name, h) + return h +} + +// GetOrCreatePrometheusHistogram returns registered prometheus histogram in s +// with the given name or creates new histogram if s doesn't contain histogram +// with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewPrometheusHistogram instead of GetOrCreatePrometheusHistogram. +func (s *Set) GetOrCreatePrometheusHistogram(name string) *PrometheusHistogram { + return s.GetOrCreatePrometheusHistogramExt(name, PrometheusHistogramDefaultBuckets) +} + +// GetOrCreatePrometheusHistogramExt returns registered prometheus histogram in +// s with the given name or creates new histogram if s doesn't contain +// histogram with the given name. +// +// name must be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} +// +// The returned histogram is safe to use from concurrent goroutines. +// +// Performance tip: prefer NewPrometheusHistogramExt instead of GetOrCreatePrometheusHistogramExt. +func (s *Set) GetOrCreatePrometheusHistogramExt(name string, upperBounds []float64) *PrometheusHistogram { + s.mu.Lock() + nm := s.m[name] + s.mu.Unlock() + if nm == nil { + // Slow path - create and register missing histogram. + if err := ValidateMetric(name); err != nil { + panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) + } + nmNew := &namedMetric{ + name: name, + metric: newPrometheusHistogram(upperBounds), + } + s.mu.Lock() + nm = s.m[name] + if nm == nil { + nm = nmNew + s.m[name] = nm + s.a = append(s.a, nm) + } + s.mu.Unlock() + } + h, ok := nm.metric.(*PrometheusHistogram) + if !ok { + panic(fmt.Errorf("BUG: metric %q isn't a PrometheusHistogram. It is %T", name, nm.metric)) + } + return h +} + // NewCounter registers and returns new counter with the given name in the s. // // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned counter is safe to use from concurrent goroutines. func (s *Set) NewCounter(name string) *Counter { @@ -133,9 +243,9 @@ func (s *Set) NewCounter(name string) *Counter { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned counter is safe to use from concurrent goroutines. // @@ -146,7 +256,7 @@ func (s *Set) GetOrCreateCounter(name string) *Counter { s.mu.Unlock() if nm == nil { // Slow path - create and register missing counter. - if err := validateMetric(name); err != nil { + if err := ValidateMetric(name); err != nil { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } nmNew := &namedMetric{ @@ -174,9 +284,9 @@ func (s *Set) GetOrCreateCounter(name string) *Counter { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned FloatCounter is safe to use from concurrent goroutines. func (s *Set) NewFloatCounter(name string) *FloatCounter { @@ -191,9 +301,9 @@ func (s *Set) NewFloatCounter(name string) *FloatCounter { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned FloatCounter is safe to use from concurrent goroutines. // @@ -204,7 +314,7 @@ func (s *Set) GetOrCreateFloatCounter(name string) *FloatCounter { s.mu.Unlock() if nm == nil { // Slow path - create and register missing counter. - if err := validateMetric(name); err != nil { + if err := ValidateMetric(name); err != nil { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } nmNew := &namedMetric{ @@ -233,17 +343,14 @@ func (s *Set) GetOrCreateFloatCounter(name string) *FloatCounter { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // f must be safe for concurrent calls. // // The returned gauge is safe to use from concurrent goroutines. func (s *Set) NewGauge(name string, f func() float64) *Gauge { - if f == nil { - panic(fmt.Errorf("BUG: f cannot be nil")) - } g := &Gauge{ f: f, } @@ -257,9 +364,9 @@ func (s *Set) NewGauge(name string, f func() float64) *Gauge { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned gauge is safe to use from concurrent goroutines. // @@ -270,10 +377,7 @@ func (s *Set) GetOrCreateGauge(name string, f func() float64) *Gauge { s.mu.Unlock() if nm == nil { // Slow path - create and register missing gauge. - if f == nil { - panic(fmt.Errorf("BUG: f cannot be nil")) - } - if err := validateMetric(name); err != nil { + if err := ValidateMetric(name); err != nil { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } nmNew := &namedMetric{ @@ -303,9 +407,9 @@ func (s *Set) GetOrCreateGauge(name string, f func() float64) *Gauge { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned summary is safe to use from concurrent goroutines. func (s *Set) NewSummary(name string) *Summary { @@ -318,13 +422,13 @@ func (s *Set) NewSummary(name string) *Summary { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned summary is safe to use from concurrent goroutines. func (s *Set) NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary { - if err := validateMetric(name); err != nil { + if err := ValidateMetric(name); err != nil { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } sm := newSummary(window, quantiles) @@ -334,7 +438,7 @@ func (s *Set) NewSummaryExt(name string, window time.Duration, quantiles []float // checks in tests defer s.mu.Unlock() - s.mustRegisterLocked(name, sm) + s.mustRegisterLocked(name, sm, false) registerSummaryLocked(sm) s.registerSummaryQuantilesLocked(name, sm) s.summaries = append(s.summaries, sm) @@ -347,9 +451,9 @@ func (s *Set) NewSummaryExt(name string, window time.Duration, quantiles []float // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned summary is safe to use from concurrent goroutines. // @@ -365,9 +469,9 @@ func (s *Set) GetOrCreateSummary(name string) *Summary { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned summary is safe to use from concurrent goroutines. // @@ -378,7 +482,7 @@ func (s *Set) GetOrCreateSummaryExt(name string, window time.Duration, quantiles s.mu.Unlock() if nm == nil { // Slow path - create and register missing summary. - if err := validateMetric(name); err != nil { + if err := ValidateMetric(name); err != nil { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } sm := newSummary(window, quantiles) @@ -418,30 +522,31 @@ func (s *Set) registerSummaryQuantilesLocked(name string, sm *Summary) { sm: sm, idx: i, } - s.mustRegisterLocked(quantileValueName, qv) + s.mustRegisterLocked(quantileValueName, qv, true) } } func (s *Set) registerMetric(name string, m metric) { - if err := validateMetric(name); err != nil { + if err := ValidateMetric(name); err != nil { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } s.mu.Lock() // defer will unlock in case of panic // checks in test defer s.mu.Unlock() - s.mustRegisterLocked(name, m) + s.mustRegisterLocked(name, m, false) } -// mustRegisterLocked registers given metric with -// the given name. Panics if the given name was -// already registered before. -func (s *Set) mustRegisterLocked(name string, m metric) { +// mustRegisterLocked registers given metric with the given name. +// +// Panics if the given name was already registered before. +func (s *Set) mustRegisterLocked(name string, m metric, isAux bool) { nm, ok := s.m[name] if !ok { nm = &namedMetric{ name: name, metric: m, + isAux: isAux, } s.m[name] = nm s.a = append(s.a, nm) @@ -463,8 +568,16 @@ func (s *Set) UnregisterMetric(name string) bool { if !ok { return false } - m := nm.metric + if nm.isAux { + // Do not allow deleting auxiliary metrics such as summary_metric{quantile="..."} + // Such metrics must be deleted via parent metric name, e.g. summary_metric . + return false + } + return s.unregisterMetricLocked(nm) +} +func (s *Set) unregisterMetricLocked(nm *namedMetric) bool { + name := nm.name delete(s.m, name) deleteFromList := func(metricName string) { @@ -480,9 +593,9 @@ func (s *Set) UnregisterMetric(name string) bool { // remove metric from s.a deleteFromList(name) - sm, ok := m.(*Summary) + sm, ok := nm.metric.(*Summary) if !ok { - // There is no need in cleaning up summary. + // There is no need in cleaning up non-summary metrics. return true } @@ -509,11 +622,47 @@ func (s *Set) UnregisterMetric(name string) bool { return true } -// ListMetricNames returns a list of all the metrics in s. +// UnregisterAllMetrics de-registers all metrics registered in s. +// +// It also de-registers writeMetrics callbacks passed to RegisterMetricsWriter. +func (s *Set) UnregisterAllMetrics() { + metricNames := s.ListMetricNames() + for _, name := range metricNames { + s.UnregisterMetric(name) + } + + s.mu.Lock() + s.metricsWriters = nil + s.mu.Unlock() +} + +// ListMetricNames returns sorted list of all the metrics in s. +// +// The returned list doesn't include metrics generated by metricsWriter passed to RegisterMetricsWriter. func (s *Set) ListMetricNames() []string { - var list []string - for name := range s.m { - list = append(list, name) + s.mu.Lock() + defer s.mu.Unlock() + metricNames := make([]string, 0, len(s.m)) + for _, nm := range s.m { + if nm.isAux { + continue + } + metricNames = append(metricNames, nm.name) } - return list + sort.Strings(metricNames) + return metricNames +} + +// RegisterMetricsWriter registers writeMetrics callback for including metrics in the output generated by s.WritePrometheus. +// +// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments. +// The last line generated by writeMetrics must end with \n. +// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format +// +// It is OK to reguster multiple writeMetrics callbacks - all of them will be called sequentially for gererating the output at s.WritePrometheus. +func (s *Set) RegisterMetricsWriter(writeMetrics func(w io.Writer)) { + s.mu.Lock() + defer s.mu.Unlock() + + s.metricsWriters = append(s.metricsWriters, writeMetrics) } diff --git a/vendor/github.com/VictoriaMetrics/metrics/set_example_test.go b/vendor/github.com/VictoriaMetrics/metrics/set_example_test.go index 50845d37..17c1b365 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/set_example_test.go +++ b/vendor/github.com/VictoriaMetrics/metrics/set_example_test.go @@ -23,3 +23,56 @@ func ExampleSet() { // set_counter 1 // set_gauge{foo="bar"} 42 } + +func ExampleExposeMetadata() { + metrics.ExposeMetadata(true) + defer metrics.ExposeMetadata(false) + + s := metrics.NewSet() + + sc := s.NewCounter("set_counter") + sc.Inc() + + s.NewGauge(`unused_bytes{foo="bar"}`, func() float64 { return 58 }) + s.NewGauge(`used_bytes{foo="bar"}`, func() float64 { return 42 }) + s.NewGauge(`used_bytes{foo="baz"}`, func() float64 { return 43 }) + + h := s.NewHistogram(`request_duration_seconds{path="/foo/bar"}`) + h.Update(1) + h.Update(2) + + s.NewSummary("response_size_bytes").Update(1) + + // Dump metrics from s. + var bb bytes.Buffer + s.WritePrometheus(&bb) + fmt.Printf("set metrics:\n%s\n", bb.String()) + + // Output: + // set metrics: + // # HELP request_duration_seconds + // # TYPE request_duration_seconds histogram + // request_duration_seconds_bucket{path="/foo/bar",vmrange="8.799e-01...1.000e+00"} 1 + // request_duration_seconds_bucket{path="/foo/bar",vmrange="1.896e+00...2.154e+00"} 1 + // request_duration_seconds_sum{path="/foo/bar"} 3 + // request_duration_seconds_count{path="/foo/bar"} 2 + // # HELP response_size_bytes + // # TYPE response_size_bytes summary + // response_size_bytes_sum 1 + // response_size_bytes_count 1 + // response_size_bytes{quantile="0.5"} 1 + // response_size_bytes{quantile="0.9"} 1 + // response_size_bytes{quantile="0.97"} 1 + // response_size_bytes{quantile="0.99"} 1 + // response_size_bytes{quantile="1"} 1 + // # HELP set_counter + // # TYPE set_counter counter + // set_counter 1 + // # HELP unused_bytes + // # TYPE unused_bytes gauge + // unused_bytes{foo="bar"} 58 + // # HELP used_bytes + // # TYPE used_bytes gauge + // used_bytes{foo="bar"} 42 + // used_bytes{foo="baz"} 43 +} diff --git a/vendor/github.com/VictoriaMetrics/metrics/set_test.go b/vendor/github.com/VictoriaMetrics/metrics/set_test.go index c954d873..66ed07a4 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/set_test.go +++ b/vendor/github.com/VictoriaMetrics/metrics/set_test.go @@ -64,6 +64,27 @@ func TestSetListMetricNames(t *testing.T) { } } +func TestSetUnregisterAllMetrics(t *testing.T) { + s := NewSet() + for j := 0; j < 3; j++ { + expectedMetricsCount := 0 + for i := 0; i < 10; i++ { + _ = s.NewCounter(fmt.Sprintf("counter_%d", i)) + _ = s.NewSummary(fmt.Sprintf("summary_%d", i)) + _ = s.NewHistogram(fmt.Sprintf("histogram_%d", i)) + _ = s.NewGauge(fmt.Sprintf("gauge_%d", i), func() float64 { return 0 }) + expectedMetricsCount += 4 + } + if mns := s.ListMetricNames(); len(mns) != expectedMetricsCount { + t.Fatalf("unexpected number of metric names on iteration %d; got %d; want %d;\nmetric names:\n%q", j, len(mns), expectedMetricsCount, mns) + } + s.UnregisterAllMetrics() + if mns := s.ListMetricNames(); len(mns) != 0 { + t.Fatalf("unexpected metric names after UnregisterAllMetrics call on iteration %d: %q", j, mns) + } + } +} + func TestSetUnregisterMetric(t *testing.T) { s := NewSet() const cName, smName = "counter_1", "summary_1" diff --git a/vendor/github.com/VictoriaMetrics/metrics/summary.go b/vendor/github.com/VictoriaMetrics/metrics/summary.go index 0f01e9ae..0469b983 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/summary.go +++ b/vendor/github.com/VictoriaMetrics/metrics/summary.go @@ -36,9 +36,9 @@ type Summary struct { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned summary is safe to use from concurrent goroutines. func NewSummary(name string) *Summary { @@ -51,9 +51,9 @@ func NewSummary(name string) *Summary { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned summary is safe to use from concurrent goroutines. func NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary { @@ -119,6 +119,10 @@ func (sm *Summary) marshalTo(prefix string, w io.Writer) { } } +func (sm *Summary) metricType() string { + return "summary" +} + func splitMetricName(name string) (string, string) { n := strings.IndexByte(name, '{') if n < 0 { @@ -140,9 +144,9 @@ func (sm *Summary) updateQuantiles() { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned summary is safe to use from concurrent goroutines. // @@ -158,9 +162,9 @@ func GetOrCreateSummary(name string) *Summary { // name must be valid Prometheus-compatible metric with possible labels. // For instance, // -// * foo -// * foo{bar="baz"} -// * foo{bar="baz",aaa="b"} +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} // // The returned summary is safe to use from concurrent goroutines. // @@ -196,11 +200,23 @@ func (qv *quantileValue) marshalTo(prefix string, w io.Writer) { } } +func (qv *quantileValue) metricType() string { + return "unsupported" +} + func addTag(name, tag string) string { if len(name) == 0 || name[len(name)-1] != '}' { return fmt.Sprintf("%s{%s}", name, tag) } - return fmt.Sprintf("%s,%s}", name[:len(name)-1], tag) + name = name[:len(name)-1] + if len(name) == 0 { + panic(fmt.Errorf("BUG: metric name cannot be empty")) + } + if name[len(name)-1] == '{' { + // case for empty labels set metric_name{} + return fmt.Sprintf("%s%s}", name, tag) + } + return fmt.Sprintf("%s,%s}", name, tag) } func registerSummaryLocked(sm *Summary) { diff --git a/vendor/github.com/VictoriaMetrics/metrics/summary_test.go b/vendor/github.com/VictoriaMetrics/metrics/summary_test.go index 9182a653..bde323f8 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/summary_test.go +++ b/vendor/github.com/VictoriaMetrics/metrics/summary_test.go @@ -77,6 +77,20 @@ func TestSummaryWithTags(t *testing.T) { } } +func TestSummaryWithEmptyTags(t *testing.T) { + name := `TestSummary{}` + s := NewSummary(name) + s.Update(123) + + var bb bytes.Buffer + WritePrometheus(&bb, false) + result := bb.String() + namePrefixWithTag := `TestSummary{quantile="` + if !strings.Contains(result, namePrefixWithTag) { + t.Fatalf("missing summary prefix %s in the WritePrometheus output; got\n%s", namePrefixWithTag, result) + } +} + func TestSummaryInvalidQuantiles(t *testing.T) { name := "SummaryInvalidQuantiles" expectPanic(t, name, func() { diff --git a/vendor/github.com/VictoriaMetrics/metrics/validator.go b/vendor/github.com/VictoriaMetrics/metrics/validator.go index 9960189a..26652ac4 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/validator.go +++ b/vendor/github.com/VictoriaMetrics/metrics/validator.go @@ -6,7 +6,14 @@ import ( "strings" ) -func validateMetric(s string) error { +// ValidateMetric validates provided string +// to be valid Prometheus-compatible metric with possible labels. +// For instance, +// +// - foo +// - foo{bar="baz"} +// - foo{bar="baz",aaa="b"} +func ValidateMetric(s string) error { if len(s) == 0 { return fmt.Errorf("metric cannot be empty") } diff --git a/vendor/github.com/VictoriaMetrics/metrics/validator_test.go b/vendor/github.com/VictoriaMetrics/metrics/validator_test.go index 8a5d7347..497b27f9 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/validator_test.go +++ b/vendor/github.com/VictoriaMetrics/metrics/validator_test.go @@ -7,7 +7,7 @@ import ( func TestValidateMetricSuccess(t *testing.T) { f := func(s string) { t.Helper() - if err := validateMetric(s); err != nil { + if err := ValidateMetric(s); err != nil { t.Fatalf("cannot validate %q: %s", s, err) } } @@ -24,7 +24,7 @@ func TestValidateMetricSuccess(t *testing.T) { func TestValidateMetricError(t *testing.T) { f := func(s string) { t.Helper() - if err := validateMetric(s); err == nil { + if err := ValidateMetric(s); err == nil { t.Fatalf("expecting non-nil error when validating %q", s) } } diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/.travis.yml b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/.travis.yml deleted file mode 100644 index 336ccde0..00000000 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: go - -go: - - 1.7 - - 1.8 - -script: - # build test for supported platforms - - GOOS=linux go build - - GOOS=darwin go build - - GOOS=freebsd go build - - GOARCH=386 go build - - # run tests on a standard platform - - go test -v ./... - diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/LICENSE b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/LICENSE deleted file mode 100644 index a2b05f66..00000000 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017 Aliaksandr Valialkin - -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. diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/README.md b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/README.md deleted file mode 100644 index 3d384c6b..00000000 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/README.md +++ /dev/null @@ -1,76 +0,0 @@ -[![Build Status](https://travis-ci.org/valyala/fastrand.svg)](https://travis-ci.org/valyala/fastrand) -[![GoDoc](https://godoc.org/github.com/valyala/fastrand?status.svg)](http://godoc.org/github.com/valyala/fastrand) -[![Go Report](https://goreportcard.com/badge/github.com/valyala/fastrand)](https://goreportcard.com/report/github.com/valyala/fastrand) - - -# fastrand - -Fast pseudorandom number generator. - - -# Features - -- Optimized for speed. -- Performance scales on multiple CPUs. - -# How does it work? - -It abuses [sync.Pool](https://golang.org/pkg/sync/#Pool) for maintaining -"per-CPU" pseudorandom number generators. - -TODO: firgure out how to use real per-CPU pseudorandom number generators. - - -# Benchmark results - - -``` -$ GOMAXPROCS=1 go test -bench=. github.com/valyala/fastrand -goos: linux -goarch: amd64 -pkg: github.com/valyala/fastrand -BenchmarkUint32n 50000000 29.7 ns/op -BenchmarkRNGUint32n 200000000 6.50 ns/op -BenchmarkRNGUint32nWithLock 100000000 21.5 ns/op -BenchmarkMathRandInt31n 50000000 31.8 ns/op -BenchmarkMathRandRNGInt31n 100000000 17.9 ns/op -BenchmarkMathRandRNGInt31nWithLock 50000000 30.2 ns/op -PASS -ok github.com/valyala/fastrand 10.634s -``` - -``` -$ GOMAXPROCS=2 go test -bench=. github.com/valyala/fastrand -goos: linux -goarch: amd64 -pkg: github.com/valyala/fastrand -BenchmarkUint32n-2 100000000 17.6 ns/op -BenchmarkRNGUint32n-2 500000000 3.36 ns/op -BenchmarkRNGUint32nWithLock-2 50000000 32.0 ns/op -BenchmarkMathRandInt31n-2 20000000 51.2 ns/op -BenchmarkMathRandRNGInt31n-2 100000000 11.0 ns/op -BenchmarkMathRandRNGInt31nWithLock-2 20000000 91.0 ns/op -PASS -ok github.com/valyala/fastrand 9.543s -``` - -``` -$ GOMAXPROCS=4 go test -bench=. github.com/valyala/fastrand -goos: linux -goarch: amd64 -pkg: github.com/valyala/fastrand -BenchmarkUint32n-4 100000000 14.2 ns/op -BenchmarkRNGUint32n-4 500000000 3.30 ns/op -BenchmarkRNGUint32nWithLock-4 20000000 88.7 ns/op -BenchmarkMathRandInt31n-4 10000000 145 ns/op -BenchmarkMathRandRNGInt31n-4 200000000 8.35 ns/op -BenchmarkMathRandRNGInt31nWithLock-4 20000000 102 ns/op -PASS -ok github.com/valyala/fastrand 11.534s -``` - -As you can see, [fastrand.Uint32n](https://godoc.org/github.com/valyala/fastrand#Uint32n) -scales on multiple CPUs, while [rand.Int31n](https://golang.org/pkg/math/rand/#Int31n) -doesn't scale. Their performance is comparable on `GOMAXPROCS=1`, -but `fastrand.Uint32n` runs 3x faster than `rand.Int31n` on `GOMAXPROCS=2` -and 10x faster than `rand.Int31n` on `GOMAXPROCS=4`. diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/fastrand.go b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/fastrand.go deleted file mode 100644 index 3ea9177c..00000000 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/fastrand.go +++ /dev/null @@ -1,74 +0,0 @@ -// Package fastrand implements fast pesudorandom number generator -// that should scale well on multi-CPU systems. -// -// Use crypto/rand instead of this package for generating -// cryptographically secure random numbers. -package fastrand - -import ( - "sync" - "time" -) - -// Uint32 returns pseudorandom uint32. -// -// It is safe calling this function from concurrent goroutines. -func Uint32() uint32 { - v := rngPool.Get() - if v == nil { - v = &RNG{} - } - r := v.(*RNG) - x := r.Uint32() - rngPool.Put(r) - return x -} - -var rngPool sync.Pool - -// Uint32n returns pseudorandom uint32 in the range [0..maxN). -// -// It is safe calling this function from concurrent goroutines. -func Uint32n(maxN uint32) uint32 { - x := Uint32() - // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ - return uint32((uint64(x) * uint64(maxN)) >> 32) -} - -// RNG is a pseudorandom number generator. -// -// It is unsafe to call RNG methods from concurrent goroutines. -type RNG struct { - x uint32 -} - -// Uint32 returns pseudorandom uint32. -// -// It is unsafe to call this method from concurrent goroutines. -func (r *RNG) Uint32() uint32 { - for r.x == 0 { - r.x = getRandomUint32() - } - - // See https://en.wikipedia.org/wiki/Xorshift - x := r.x - x ^= x << 13 - x ^= x >> 17 - x ^= x << 5 - r.x = x - return x -} - -// Uint32n returns pseudorandom uint32 in the range [0..maxN). -// -// It is unsafe to call this method from concurrent goroutines. -func (r *RNG) Uint32n(maxN uint32) uint32 { - x := r.Uint32() - // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ - return uint32((uint64(x) * uint64(maxN)) >> 32) -} - -func getRandomUint32() uint32 { - x := time.Now().UnixNano() - return uint32((x >> 32) ^ x) -} diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/go.mod b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/go.mod deleted file mode 100644 index 958910b7..00000000 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/fastrand/go.mod +++ /dev/null @@ -1 +0,0 @@ -module github.com/valyala/fastrand diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/LICENSE b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/LICENSE deleted file mode 100644 index 902bcad0..00000000 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2019 Aliaksandr Valialkin - -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. diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/README.md b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/README.md deleted file mode 100644 index 6d4eb59c..00000000 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/README.md +++ /dev/null @@ -1,9 +0,0 @@ -[![GoDoc](https://godoc.org/github.com/valyala/histogram?status.svg)](http://godoc.org/github.com/valyala/histogram) -[![Go Report](https://goreportcard.com/badge/github.com/valyala/histogram)](https://goreportcard.com/report/github.com/valyala/histogram) - - -# histogram - -Fast histograms for Go. - -See [docs](https://godoc.org/github.com/valyala/histogram). diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.mod b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.mod deleted file mode 100644 index 984efbe6..00000000 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/valyala/histogram - -go 1.12 - -require github.com/valyala/fastrand v1.0.0 diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.sum b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.sum deleted file mode 100644 index 2b3e848a..00000000 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= -github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/histogram.go b/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/histogram.go deleted file mode 100644 index e90dd703..00000000 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/github.com/valyala/histogram/histogram.go +++ /dev/null @@ -1,127 +0,0 @@ -// Package histogram provides building blocks for fast histograms. -package histogram - -import ( - "math" - "sort" - "sync" - - "github.com/valyala/fastrand" -) - -var ( - infNeg = math.Inf(-1) - infPos = math.Inf(1) - nan = math.NaN() -) - -// Fast is a fast histogram. -// -// It cannot be used from concurrently running goroutines without -// external synchronization. -type Fast struct { - max float64 - min float64 - count uint64 - - a []float64 - tmp []float64 - rng fastrand.RNG -} - -// NewFast returns new fast histogram. -func NewFast() *Fast { - f := &Fast{} - f.Reset() - return f -} - -// Reset resets the histogram. -func (f *Fast) Reset() { - f.max = infNeg - f.min = infPos - f.count = 0 - if len(f.a) > 0 { - f.a = f.a[:0] - f.tmp = f.tmp[:0] - } else { - // Free up memory occupied by unused histogram. - f.a = nil - f.tmp = nil - } -} - -// Update updates the f with v. -func (f *Fast) Update(v float64) { - if v > f.max { - f.max = v - } - if v < f.min { - f.min = v - } - - f.count++ - if len(f.a) < maxSamples { - f.a = append(f.a, v) - return - } - if n := int(f.rng.Uint32n(uint32(f.count))); n < len(f.a) { - f.a[n] = v - } -} - -const maxSamples = 1000 - -// Quantile returns the quantile value for the given phi. -func (f *Fast) Quantile(phi float64) float64 { - f.tmp = append(f.tmp[:0], f.a...) - sort.Float64s(f.tmp) - return f.quantile(phi) -} - -// Quantiles appends quantile values to dst for the given phis. -func (f *Fast) Quantiles(dst, phis []float64) []float64 { - f.tmp = append(f.tmp[:0], f.a...) - sort.Float64s(f.tmp) - for _, phi := range phis { - q := f.quantile(phi) - dst = append(dst, q) - } - return dst -} - -func (f *Fast) quantile(phi float64) float64 { - if len(f.tmp) == 0 || math.IsNaN(phi) { - return nan - } - if phi <= 0 { - return f.min - } - if phi >= 1 { - return f.max - } - idx := uint(phi*float64(len(f.tmp)-1) + 0.5) - if idx >= uint(len(f.tmp)) { - idx = uint(len(f.tmp) - 1) - } - return f.tmp[idx] -} - -// GetFast returns a histogram from a pool. -func GetFast() *Fast { - v := fastPool.Get() - if v == nil { - return NewFast() - } - return v.(*Fast) -} - -// PutFast puts hf to the pool. -// -// hf cannot be used after this call. -func PutFast(f *Fast) { - f.Reset() - fastPool.Put(f) -} - -var fastPool sync.Pool diff --git a/vendor/github.com/VictoriaMetrics/metrics/vendor/modules.txt b/vendor/github.com/VictoriaMetrics/metrics/vendor/modules.txt index f915051e..682dbd57 100644 --- a/vendor/github.com/VictoriaMetrics/metrics/vendor/modules.txt +++ b/vendor/github.com/VictoriaMetrics/metrics/vendor/modules.txt @@ -1,4 +1,9 @@ -# github.com/valyala/fastrand v1.0.0 +# github.com/valyala/fastrand v1.1.0 +## explicit github.com/valyala/fastrand -# github.com/valyala/histogram v1.1.2 +# github.com/valyala/histogram v1.2.0 +## explicit; go 1.12 github.com/valyala/histogram +# golang.org/x/sys v0.15.0 +## explicit; go 1.18 +golang.org/x/sys/windows diff --git a/vendor/github.com/valyala/fastrand/fastrand.go b/vendor/github.com/valyala/fastrand/fastrand.go index 3ea9177c..8d25b560 100644 --- a/vendor/github.com/valyala/fastrand/fastrand.go +++ b/vendor/github.com/valyala/fastrand/fastrand.go @@ -68,6 +68,11 @@ func (r *RNG) Uint32n(maxN uint32) uint32 { return uint32((uint64(x) * uint64(maxN)) >> 32) } +// Seed sets the r state to n. +func (r *RNG) Seed(n uint32) { + r.x = n +} + func getRandomUint32() uint32 { x := time.Now().UnixNano() return uint32((x >> 32) ^ x) diff --git a/vendor/github.com/valyala/fastrand/fastrand_test.go b/vendor/github.com/valyala/fastrand/fastrand_test.go new file mode 100644 index 00000000..cea2cfa0 --- /dev/null +++ b/vendor/github.com/valyala/fastrand/fastrand_test.go @@ -0,0 +1,90 @@ +package fastrand + +import ( + "testing" +) + +func TestRNGSeed(t *testing.T) { + var r RNG + for _, seed := range []uint32{1, 2, 1234, 432432, 34324432} { + r.Seed(seed) + m := make(map[uint32]struct{}) + for i := 0; i < 1e6; i++ { + n := r.Uint32() + if _, ok := m[n]; ok { + t.Fatalf("number %v already exists", n) + } + m[n] = struct{}{} + } + } +} + +func TestUint32(t *testing.T) { + m := make(map[uint32]struct{}) + for i := 0; i < 1e6; i++ { + n := Uint32() + if _, ok := m[n]; ok { + t.Fatalf("number %v already exists", n) + } + m[n] = struct{}{} + } +} + +func TestRNGUint32(t *testing.T) { + var r RNG + m := make(map[uint32]struct{}) + for i := 0; i < 1e6; i++ { + n := r.Uint32() + if _, ok := m[n]; ok { + t.Fatalf("number %v already exists", n) + } + m[n] = struct{}{} + } +} + +func TestUint32n(t *testing.T) { + m := make(map[uint32]int) + for i := 0; i < 1e6; i++ { + n := Uint32n(1e2) + if n >= 1e2 { + t.Fatalf("n > 1000: %v", n) + } + m[n]++ + } + + // check distribution + avg := 1e6 / 1e2 + for k, v := range m { + p := (float64(v) - float64(avg)) / float64(avg) + if p < 0 { + p = -p + } + if p > 0.05 { + t.Fatalf("skew more than 5%% for k=%v: %v", k, p*100) + } + } +} + +func TestRNGUint32n(t *testing.T) { + var r RNG + m := make(map[uint32]int) + for i := 0; i < 1e6; i++ { + n := r.Uint32n(1e2) + if n >= 1e2 { + t.Fatalf("n > 1000: %v", n) + } + m[n]++ + } + + // check distribution + avg := 1e6 / 1e2 + for k, v := range m { + p := (float64(v) - float64(avg)) / float64(avg) + if p < 0 { + p = -p + } + if p > 0.05 { + t.Fatalf("skew more than 5%% for k=%v: %v", k, p*100) + } + } +} diff --git a/vendor/github.com/valyala/fastrand/fastrand_timing_test.go b/vendor/github.com/valyala/fastrand/fastrand_timing_test.go new file mode 100644 index 00000000..8f153217 --- /dev/null +++ b/vendor/github.com/valyala/fastrand/fastrand_timing_test.go @@ -0,0 +1,130 @@ +package fastrand + +import ( + "math/rand" + "sync" + "sync/atomic" + "testing" + "unsafe" +) + +// BenchSink prevents the compiler from optimizing away benchmark loops. +var BenchSink uint32 + +func BenchmarkUint32n(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + s := uint32(0) + for pb.Next() { + s += Uint32n(1e6) + } + atomic.AddUint32(&BenchSink, s) + }) +} + +func BenchmarkRNGUint32n(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + var r RNG + s := uint32(0) + for pb.Next() { + s += r.Uint32n(1e6) + } + atomic.AddUint32(&BenchSink, s) + }) +} + +func BenchmarkRNGUint32nWithLock(b *testing.B) { + var r RNG + var rMu sync.Mutex + b.RunParallel(func(pb *testing.PB) { + s := uint32(0) + for pb.Next() { + rMu.Lock() + s += r.Uint32n(1e6) + rMu.Unlock() + } + atomic.AddUint32(&BenchSink, s) + }) +} + +func BenchmarkRNGUint32nArray(b *testing.B) { + var rr [64]struct { + r RNG + mu sync.Mutex + + // pad prevents from false sharing + pad [64 - (unsafe.Sizeof(RNG{})+unsafe.Sizeof(sync.Mutex{}))%64]byte + } + var n uint32 + b.RunParallel(func(pb *testing.PB) { + s := uint32(0) + for pb.Next() { + idx := atomic.AddUint32(&n, 1) + r := &rr[idx%uint32(len(rr))] + r.mu.Lock() + s += r.r.Uint32n(1e6) + r.mu.Unlock() + } + atomic.AddUint32(&BenchSink, s) + }) +} + +func BenchmarkMathRandInt31n(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + s := uint32(0) + for pb.Next() { + s += uint32(rand.Int31n(1e6)) + } + atomic.AddUint32(&BenchSink, s) + }) +} + +func BenchmarkMathRandRNGInt31n(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + r := rand.New(rand.NewSource(42)) + s := uint32(0) + for pb.Next() { + s += uint32(r.Int31n(1e6)) + } + atomic.AddUint32(&BenchSink, s) + }) +} + +func BenchmarkMathRandRNGInt31nWithLock(b *testing.B) { + r := rand.New(rand.NewSource(42)) + var rMu sync.Mutex + b.RunParallel(func(pb *testing.PB) { + s := uint32(0) + for pb.Next() { + rMu.Lock() + s += uint32(r.Int31n(1e6)) + rMu.Unlock() + } + atomic.AddUint32(&BenchSink, s) + }) +} + +func BenchmarkMathRandRNGInt31nArray(b *testing.B) { + var rr [64]struct { + r *rand.Rand + mu sync.Mutex + + // pad prevents from false sharing + pad [64 - (unsafe.Sizeof(RNG{})+unsafe.Sizeof(sync.Mutex{}))%64]byte + } + for i := range rr { + rr[i].r = rand.New(rand.NewSource(int64(i))) + } + + var n uint32 + b.RunParallel(func(pb *testing.PB) { + s := uint32(0) + for pb.Next() { + idx := atomic.AddUint32(&n, 1) + r := &rr[idx%uint32(len(rr))] + r.mu.Lock() + s += uint32(r.r.Int31n(1e6)) + r.mu.Unlock() + } + atomic.AddUint32(&BenchSink, s) + }) +} diff --git a/vendor/github.com/valyala/histogram/go.mod b/vendor/github.com/valyala/histogram/go.mod index 984efbe6..cc65b006 100644 --- a/vendor/github.com/valyala/histogram/go.mod +++ b/vendor/github.com/valyala/histogram/go.mod @@ -2,4 +2,4 @@ module github.com/valyala/histogram go 1.12 -require github.com/valyala/fastrand v1.0.0 +require github.com/valyala/fastrand v1.1.0 diff --git a/vendor/github.com/valyala/histogram/go.sum b/vendor/github.com/valyala/histogram/go.sum index 2b3e848a..c5ca5880 100644 --- a/vendor/github.com/valyala/histogram/go.sum +++ b/vendor/github.com/valyala/histogram/go.sum @@ -1,2 +1,2 @@ -github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= -github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= +github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= +github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= diff --git a/vendor/github.com/valyala/histogram/histogram.go b/vendor/github.com/valyala/histogram/histogram.go index e90dd703..71be2afc 100644 --- a/vendor/github.com/valyala/histogram/histogram.go +++ b/vendor/github.com/valyala/histogram/histogram.go @@ -49,6 +49,10 @@ func (f *Fast) Reset() { f.a = nil f.tmp = nil } + // Reset rng state in order to get repeatable results + // for the same sequence of values passed to Fast.Update. + // See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1612 + f.rng.Seed(1) } // Update updates the f with v. diff --git a/vendor/github.com/valyala/histogram/histogram_test.go b/vendor/github.com/valyala/histogram/histogram_test.go index d1b5a120..94730975 100644 --- a/vendor/github.com/valyala/histogram/histogram_test.go +++ b/vendor/github.com/valyala/histogram/histogram_test.go @@ -57,3 +57,24 @@ func TestFastOverflow(t *testing.T) { t.Fatalf("unexpected value for phi=NaN; got %v; want %v", q, nan) } } + +func TestFastRepeatableResults(t *testing.T) { + f := GetFast() + defer PutFast(f) + + for i := 0; i < maxSamples*10; i++ { + f.Update(float64(i)) + } + q1 := f.Quantile(0.95) + + for j := 0; j < 10; j++ { + f.Reset() + for i := 0; i < maxSamples*10; i++ { + f.Update(float64(i)) + } + q2 := f.Quantile(0.95) + if q2 != q1 { + t.Fatalf("unexpected quantile value; got %g; want %g", q2, q1) + } + } +} diff --git a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/.travis.yml b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/.travis.yml deleted file mode 100644 index 336ccde0..00000000 --- a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: go - -go: - - 1.7 - - 1.8 - -script: - # build test for supported platforms - - GOOS=linux go build - - GOOS=darwin go build - - GOOS=freebsd go build - - GOARCH=386 go build - - # run tests on a standard platform - - go test -v ./... - diff --git a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/LICENSE b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/LICENSE deleted file mode 100644 index a2b05f66..00000000 --- a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017 Aliaksandr Valialkin - -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. diff --git a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/README.md b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/README.md deleted file mode 100644 index 3d384c6b..00000000 --- a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/README.md +++ /dev/null @@ -1,76 +0,0 @@ -[![Build Status](https://travis-ci.org/valyala/fastrand.svg)](https://travis-ci.org/valyala/fastrand) -[![GoDoc](https://godoc.org/github.com/valyala/fastrand?status.svg)](http://godoc.org/github.com/valyala/fastrand) -[![Go Report](https://goreportcard.com/badge/github.com/valyala/fastrand)](https://goreportcard.com/report/github.com/valyala/fastrand) - - -# fastrand - -Fast pseudorandom number generator. - - -# Features - -- Optimized for speed. -- Performance scales on multiple CPUs. - -# How does it work? - -It abuses [sync.Pool](https://golang.org/pkg/sync/#Pool) for maintaining -"per-CPU" pseudorandom number generators. - -TODO: firgure out how to use real per-CPU pseudorandom number generators. - - -# Benchmark results - - -``` -$ GOMAXPROCS=1 go test -bench=. github.com/valyala/fastrand -goos: linux -goarch: amd64 -pkg: github.com/valyala/fastrand -BenchmarkUint32n 50000000 29.7 ns/op -BenchmarkRNGUint32n 200000000 6.50 ns/op -BenchmarkRNGUint32nWithLock 100000000 21.5 ns/op -BenchmarkMathRandInt31n 50000000 31.8 ns/op -BenchmarkMathRandRNGInt31n 100000000 17.9 ns/op -BenchmarkMathRandRNGInt31nWithLock 50000000 30.2 ns/op -PASS -ok github.com/valyala/fastrand 10.634s -``` - -``` -$ GOMAXPROCS=2 go test -bench=. github.com/valyala/fastrand -goos: linux -goarch: amd64 -pkg: github.com/valyala/fastrand -BenchmarkUint32n-2 100000000 17.6 ns/op -BenchmarkRNGUint32n-2 500000000 3.36 ns/op -BenchmarkRNGUint32nWithLock-2 50000000 32.0 ns/op -BenchmarkMathRandInt31n-2 20000000 51.2 ns/op -BenchmarkMathRandRNGInt31n-2 100000000 11.0 ns/op -BenchmarkMathRandRNGInt31nWithLock-2 20000000 91.0 ns/op -PASS -ok github.com/valyala/fastrand 9.543s -``` - -``` -$ GOMAXPROCS=4 go test -bench=. github.com/valyala/fastrand -goos: linux -goarch: amd64 -pkg: github.com/valyala/fastrand -BenchmarkUint32n-4 100000000 14.2 ns/op -BenchmarkRNGUint32n-4 500000000 3.30 ns/op -BenchmarkRNGUint32nWithLock-4 20000000 88.7 ns/op -BenchmarkMathRandInt31n-4 10000000 145 ns/op -BenchmarkMathRandRNGInt31n-4 200000000 8.35 ns/op -BenchmarkMathRandRNGInt31nWithLock-4 20000000 102 ns/op -PASS -ok github.com/valyala/fastrand 11.534s -``` - -As you can see, [fastrand.Uint32n](https://godoc.org/github.com/valyala/fastrand#Uint32n) -scales on multiple CPUs, while [rand.Int31n](https://golang.org/pkg/math/rand/#Int31n) -doesn't scale. Their performance is comparable on `GOMAXPROCS=1`, -but `fastrand.Uint32n` runs 3x faster than `rand.Int31n` on `GOMAXPROCS=2` -and 10x faster than `rand.Int31n` on `GOMAXPROCS=4`. diff --git a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/fastrand.go b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/fastrand.go deleted file mode 100644 index 3ea9177c..00000000 --- a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/fastrand.go +++ /dev/null @@ -1,74 +0,0 @@ -// Package fastrand implements fast pesudorandom number generator -// that should scale well on multi-CPU systems. -// -// Use crypto/rand instead of this package for generating -// cryptographically secure random numbers. -package fastrand - -import ( - "sync" - "time" -) - -// Uint32 returns pseudorandom uint32. -// -// It is safe calling this function from concurrent goroutines. -func Uint32() uint32 { - v := rngPool.Get() - if v == nil { - v = &RNG{} - } - r := v.(*RNG) - x := r.Uint32() - rngPool.Put(r) - return x -} - -var rngPool sync.Pool - -// Uint32n returns pseudorandom uint32 in the range [0..maxN). -// -// It is safe calling this function from concurrent goroutines. -func Uint32n(maxN uint32) uint32 { - x := Uint32() - // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ - return uint32((uint64(x) * uint64(maxN)) >> 32) -} - -// RNG is a pseudorandom number generator. -// -// It is unsafe to call RNG methods from concurrent goroutines. -type RNG struct { - x uint32 -} - -// Uint32 returns pseudorandom uint32. -// -// It is unsafe to call this method from concurrent goroutines. -func (r *RNG) Uint32() uint32 { - for r.x == 0 { - r.x = getRandomUint32() - } - - // See https://en.wikipedia.org/wiki/Xorshift - x := r.x - x ^= x << 13 - x ^= x >> 17 - x ^= x << 5 - r.x = x - return x -} - -// Uint32n returns pseudorandom uint32 in the range [0..maxN). -// -// It is unsafe to call this method from concurrent goroutines. -func (r *RNG) Uint32n(maxN uint32) uint32 { - x := r.Uint32() - // See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ - return uint32((uint64(x) * uint64(maxN)) >> 32) -} - -func getRandomUint32() uint32 { - x := time.Now().UnixNano() - return uint32((x >> 32) ^ x) -} diff --git a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/go.mod b/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/go.mod deleted file mode 100644 index 958910b7..00000000 --- a/vendor/github.com/valyala/histogram/vendor/github.com/valyala/fastrand/go.mod +++ /dev/null @@ -1 +0,0 @@ -module github.com/valyala/fastrand diff --git a/vendor/github.com/valyala/histogram/vendor/modules.txt b/vendor/github.com/valyala/histogram/vendor/modules.txt index 9871e30d..741cbb76 100644 --- a/vendor/github.com/valyala/histogram/vendor/modules.txt +++ b/vendor/github.com/valyala/histogram/vendor/modules.txt @@ -1,2 +1,2 @@ -# github.com/valyala/fastrand v1.0.0 +# github.com/valyala/fastrand v1.1.0 github.com/valyala/fastrand