pax_global_header00006660000000000000000000000064141432536600014516gustar00rootroot0000000000000052 comment=fe0196a54c645d6071be7be1ee0878d3d1a3c01c golang-github-victoriametrics-metrics-1.18.1+ds/000077500000000000000000000000001414325366000216105ustar00rootroot00000000000000golang-github-victoriametrics-metrics-1.18.1+ds/.github/000077500000000000000000000000001414325366000231505ustar00rootroot00000000000000golang-github-victoriametrics-metrics-1.18.1+ds/.github/workflows/000077500000000000000000000000001414325366000252055ustar00rootroot00000000000000golang-github-victoriametrics-metrics-1.18.1+ds/.github/workflows/main.yml000066400000000000000000000014131414325366000266530ustar00rootroot00000000000000name: main on: - push - pull_request jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Setup Go uses: actions/setup-go@v1 with: go-version: 1.13 id: go - name: Code checkout uses: actions/checkout@v1 - name: Test run: | go test -v ./... -coverprofile=coverage.txt -covermode=atomic go test -v ./... -race - name: Build run: | GOOS=linux go build GOOS=darwin go build GOOS=freebsd go build GOOS=windows go build GOARCH=386 go build - name: Publish coverage uses: codecov/codecov-action@v1.0.6 with: token: ${{secrets.CODECOV_TOKEN}} file: ./coverage.txt golang-github-victoriametrics-metrics-1.18.1+ds/LICENSE000066400000000000000000000020731414325366000226170ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2019 VictoriaMetrics 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. golang-github-victoriametrics-metrics-1.18.1+ds/README.md000066400000000000000000000111111414325366000230620ustar00rootroot00000000000000[![Build Status](https://github.com/VictoriaMetrics/metrics/workflows/main/badge.svg)](https://github.com/VictoriaMetrics/metrics/actions) [![GoDoc](https://godoc.org/github.com/VictoriaMetrics/metrics?status.svg)](http://godoc.org/github.com/VictoriaMetrics/metrics) [![Go Report](https://goreportcard.com/badge/github.com/VictoriaMetrics/metrics)](https://goreportcard.com/report/github.com/VictoriaMetrics/metrics) [![codecov](https://codecov.io/gh/VictoriaMetrics/metrics/branch/master/graph/badge.svg)](https://codecov.io/gh/VictoriaMetrics/metrics) # metrics - lightweight package for exporting metrics in Prometheus format ### Features * Lightweight. Has minimal number of third-party dependencies and all these deps are small. See [this article](https://medium.com/@valyala/stripping-dependency-bloat-in-victoriametrics-docker-image-983fb5912b0d) for details. * Easy to use. See the [API docs](http://godoc.org/github.com/VictoriaMetrics/metrics). * Fast. * 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). ### Limitations * It doesn't implement advanced functionality from [github.com/prometheus/client_golang](https://godoc.org/github.com/prometheus/client_golang). ### Usage ```go import "github.com/VictoriaMetrics/metrics" // Register various time series. // Time series name may contain labels in Prometheus format - see below. var ( // Register counter without labels. requestsTotal = metrics.NewCounter("requests_total") // Register summary with a single label. requestDuration = metrics.NewSummary(`requests_duration_seconds{path="/foobar/baz"}`) // Register gauge with two labels. queueSize = metrics.NewGauge(`queue_size{queue="foobar",topic="baz"}`, func() float64 { return float64(foobarQueue.Len()) }) // Register histogram with a single label. responseSize = metrics.NewHistogram(`response_size{path="/foo/bar"}`) ) // ... func requestHandler() { // Increment requestTotal counter. requestsTotal.Inc() startTime := time.Now() processRequest() // Update requestDuration summary. requestDuration.UpdateDuration(startTime) // Update responseSize histogram. responseSize.Update(responseSize) } // Expose the registered metrics at `/metrics` path. http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { metrics.WritePrometheus(w, true) }) ``` See [docs](http://godoc.org/github.com/VictoriaMetrics/metrics) for more info. ### Users * `Metrics` has been extracted from [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) sources. See [this article](https://medium.com/devopslinks/victoriametrics-creating-the-best-remote-storage-for-prometheus-5d92d66787ac) for more info about `VictoriaMetrics`. ### FAQ #### Why the `metrics` API isn't compatible with `github.com/prometheus/client_golang`? Because the `github.com/prometheus/client_golang` is too complex and is hard to use. #### 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. #### How to implement [CounterVec](https://godoc.org/github.com/prometheus/client_golang/prometheus#CounterVec) in `metrics`? Just use [GetOrCreateCounter](http://godoc.org/github.com/VictoriaMetrics/metrics#GetOrCreateCounter) instead of `CounterVec.With`. See [this example](https://pkg.go.dev/github.com/VictoriaMetrics/metrics#example-Counter-Vec) for details. #### 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, 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. golang-github-victoriametrics-metrics-1.18.1+ds/counter.go000066400000000000000000000033101414325366000236130ustar00rootroot00000000000000package metrics import ( "fmt" "io" "sync/atomic" ) // NewCounter registers and returns new counter 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 counter is safe to use from concurrent goroutines. func NewCounter(name string) *Counter { return defaultSet.NewCounter(name) } // Counter is a counter. // // It may be used as a gauge if Dec and Set are called. type Counter struct { n uint64 } // Inc increments c. func (c *Counter) Inc() { atomic.AddUint64(&c.n, 1) } // Dec decrements c. func (c *Counter) Dec() { atomic.AddUint64(&c.n, ^uint64(0)) } // Add adds n to c. func (c *Counter) Add(n int) { atomic.AddUint64(&c.n, uint64(n)) } // Get returns the current value for c. func (c *Counter) Get() uint64 { return atomic.LoadUint64(&c.n) } // Set sets c value to n. func (c *Counter) Set(n uint64) { atomic.StoreUint64(&c.n, n) } // marshalTo marshals c with the given prefix to w. func (c *Counter) marshalTo(prefix string, w io.Writer) { v := c.Get() fmt.Fprintf(w, "%s %d\n", prefix, v) } // GetOrCreateCounter returns registered counter with the given name // or creates new counter if the registry doesn't contain counter 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 counter is safe to use from concurrent goroutines. // // Performance tip: prefer NewCounter instead of GetOrCreateCounter. func GetOrCreateCounter(name string) *Counter { return defaultSet.GetOrCreateCounter(name) } golang-github-victoriametrics-metrics-1.18.1+ds/counter_example_test.go000066400000000000000000000014761414325366000264000ustar00rootroot00000000000000package metrics_test import ( "fmt" "github.com/VictoriaMetrics/metrics" ) func ExampleCounter() { // Define a counter in global scope. var c = metrics.NewCounter(`metric_total{label1="value1", label2="value2"}`) // Increment the counter when needed. for i := 0; i < 10; i++ { c.Inc() } n := c.Get() fmt.Println(n) // Output: // 10 } func ExampleCounter_vec() { for i := 0; i < 3; i++ { // Dynamically construct metric name and pass it to GetOrCreateCounter. name := fmt.Sprintf(`metric_total{label1=%q, label2="%d"}`, "value1", i) metrics.GetOrCreateCounter(name).Add(i + 1) } // Read counter values. for i := 0; i < 3; i++ { name := fmt.Sprintf(`metric_total{label1=%q, label2="%d"}`, "value1", i) n := metrics.GetOrCreateCounter(name).Get() fmt.Println(n) } // Output: // 1 // 2 // 3 } golang-github-victoriametrics-metrics-1.18.1+ds/counter_test.go000066400000000000000000000031071414325366000246560ustar00rootroot00000000000000package metrics import ( "fmt" "testing" ) func TestCounterSerial(t *testing.T) { name := "CounterSerial" c := NewCounter(name) c.Inc() if n := c.Get(); n != 1 { t.Fatalf("unexpected counter value; got %d; want 1", n) } c.Set(123) if n := c.Get(); n != 123 { t.Fatalf("unexpected counter value; got %d; want 123", n) } c.Dec() if n := c.Get(); n != 122 { t.Fatalf("unexpected counter value; got %d; want 122", n) } c.Add(3) if n := c.Get(); n != 125 { t.Fatalf("unexpected counter value; got %d; want 125", n) } // Verify MarshalTo testMarshalTo(t, c, "foobar", "foobar 125\n") } func TestCounterConcurrent(t *testing.T) { name := "CounterConcurrent" c := NewCounter(name) err := testConcurrent(func() error { nPrev := c.Get() for i := 0; i < 10; i++ { c.Inc() if n := c.Get(); n <= nPrev { return fmt.Errorf("counter value must be greater than %d; got %d", nPrev, n) } } return nil }) if err != nil { t.Fatal(err) } } func TestGetOrCreateCounterSerial(t *testing.T) { name := "GetOrCreateCounterSerial" if err := testGetOrCreateCounter(name); err != nil { t.Fatal(err) } } func TestGetOrCreateCounterConcurrent(t *testing.T) { name := "GetOrCreateCounterConcurrent" err := testConcurrent(func() error { return testGetOrCreateCounter(name) }) if err != nil { t.Fatal(err) } } func testGetOrCreateCounter(name string) error { c1 := GetOrCreateCounter(name) for i := 0; i < 10; i++ { c2 := GetOrCreateCounter(name) if c1 != c2 { return fmt.Errorf("unexpected counter returned; got %p; want %p", c2, c1) } } return nil } golang-github-victoriametrics-metrics-1.18.1+ds/floatcounter.go000066400000000000000000000035431414325366000246510ustar00rootroot00000000000000package metrics import ( "fmt" "io" "sync" ) // NewFloatCounter registers and returns new counter of float64 type 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 counter is safe to use from concurrent goroutines. func NewFloatCounter(name string) *FloatCounter { return defaultSet.NewFloatCounter(name) } // FloatCounter is a float64 counter guarded by RWmutex. // // It may be used as a gauge if Add and Sub are called. type FloatCounter struct { mu sync.Mutex n float64 } // Add adds n to fc. func (fc *FloatCounter) Add(n float64) { fc.mu.Lock() fc.n += n fc.mu.Unlock() } // Sub substracts n from fc. func (fc *FloatCounter) Sub(n float64) { fc.mu.Lock() fc.n -= n fc.mu.Unlock() } // Get returns the current value for fc. func (fc *FloatCounter) Get() float64 { fc.mu.Lock() n := fc.n fc.mu.Unlock() return n } // Set sets fc value to n. func (fc *FloatCounter) Set(n float64) { fc.mu.Lock() fc.n = n fc.mu.Unlock() } // marshalTo marshals fc with the given prefix to w. func (fc *FloatCounter) marshalTo(prefix string, w io.Writer) { v := fc.Get() fmt.Fprintf(w, "%s %g\n", prefix, v) } // GetOrCreateFloatCounter returns registered FloatCounter with the given name // or creates new FloatCounter if the registry doesn't contain FloatCounter 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 FloatCounter is safe to use from concurrent goroutines. // // Performance tip: prefer NewFloatCounter instead of GetOrCreateFloatCounter. func GetOrCreateFloatCounter(name string) *FloatCounter { return defaultSet.GetOrCreateFloatCounter(name) } golang-github-victoriametrics-metrics-1.18.1+ds/floatcounter_example_test.go000066400000000000000000000016211414325366000274160ustar00rootroot00000000000000package metrics_test import ( "fmt" "github.com/VictoriaMetrics/metrics" ) func ExampleFloatCounter() { // Define a float64 counter in global scope. var fc = metrics.NewFloatCounter(`float_metric_total{label1="value1", label2="value2"}`) // Add to the counter when needed. for i := 0; i < 10; i++ { fc.Add(1.01) } n := fc.Get() fmt.Println(n) // Output: // 10.1 } func ExampleFloatCounter_vec() { for i := 0; i < 3; i++ { // Dynamically construct metric name and pass it to GetOrCreateFloatCounter. name := fmt.Sprintf(`float_metric_total{label1=%q, label2="%d"}`, "value1", i) metrics.GetOrCreateFloatCounter(name).Add(float64(i) + 1.01) } // Read counter values. for i := 0; i < 3; i++ { name := fmt.Sprintf(`float_metric_total{label1=%q, label2="%d"}`, "value1", i) n := metrics.GetOrCreateFloatCounter(name).Get() fmt.Println(n) } // Output: // 1.01 // 2.01 // 3.01 } golang-github-victoriametrics-metrics-1.18.1+ds/floatcounter_test.go000066400000000000000000000033021414325366000257010ustar00rootroot00000000000000package metrics import ( "fmt" "testing" ) func TestFloatCounterSerial(t *testing.T) { name := "FloatCounterSerial" c := NewFloatCounter(name) c.Add(0.1) if n := c.Get(); n != 0.1 { t.Fatalf("unexpected counter value; got %f; want 0.1", n) } c.Set(123.00001) if n := c.Get(); n != 123.00001 { t.Fatalf("unexpected counter value; got %f; want 123.00001", n) } c.Sub(0.00001) if n := c.Get(); n != 123 { t.Fatalf("unexpected counter value; got %f; want 123", n) } c.Add(2.002) if n := c.Get(); n != 125.002 { t.Fatalf("unexpected counter value; got %f; want 125.002", n) } // Verify MarshalTo testMarshalTo(t, c, "foobar", "foobar 125.002\n") } func TestFloatCounterConcurrent(t *testing.T) { name := "FloatCounterConcurrent" c := NewFloatCounter(name) err := testConcurrent(func() error { nPrev := c.Get() for i := 0; i < 10; i++ { c.Add(1.001) if n := c.Get(); n <= nPrev { return fmt.Errorf("counter value must be greater than %f; got %f", nPrev, n) } } return nil }) if err != nil { t.Fatal(err) } } func TestGetOrCreateFloatCounterSerial(t *testing.T) { name := "GetOrCreateFloatCounterSerial" if err := testGetOrCreateCounter(name); err != nil { t.Fatal(err) } } func TestGetOrCreateFloatCounterConcurrent(t *testing.T) { name := "GetOrCreateFloatCounterConcurrent" err := testConcurrent(func() error { return testGetOrCreateFloatCounter(name) }) if err != nil { t.Fatal(err) } } func testGetOrCreateFloatCounter(name string) error { c1 := GetOrCreateFloatCounter(name) for i := 0; i < 10; i++ { c2 := GetOrCreateFloatCounter(name) if c1 != c2 { return fmt.Errorf("unexpected counter returned; got %p; want %p", c2, c1) } } return nil } golang-github-victoriametrics-metrics-1.18.1+ds/gauge.go000066400000000000000000000032461414325366000232340ustar00rootroot00000000000000package metrics import ( "fmt" "io" ) // 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"} // // f must be safe for concurrent calls. // // The returned gauge is safe to use from concurrent goroutines. // // See also FloatCounter for working with floating-point values. func NewGauge(name string, f func() float64) *Gauge { return defaultSet.NewGauge(name, f) } // Gauge is a float64 gauge. // // See also Counter, which could be used as a gauge with Set and Dec calls. type Gauge struct { f func() float64 } // Get returns the current value for g. func (g *Gauge) Get() float64 { return g.f() } func (g *Gauge) marshalTo(prefix string, w io.Writer) { v := g.f() if float64(int64(v)) == v { // Marshal integer values without scientific notation fmt.Fprintf(w, "%s %d\n", prefix, int64(v)) } else { fmt.Fprintf(w, "%s %g\n", prefix, v) } } // GetOrCreateGauge returns registered gauge with the given name // or creates new gauge if the registry doesn't contain gauge 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 gauge is safe to use from concurrent goroutines. // // Performance tip: prefer NewGauge instead of GetOrCreateGauge. // // See also FloatCounter for working with floating-point values. func GetOrCreateGauge(name string, f func() float64) *Gauge { return defaultSet.GetOrCreateGauge(name, f) } golang-github-victoriametrics-metrics-1.18.1+ds/gauge_example_test.go000066400000000000000000000015441414325366000260050ustar00rootroot00000000000000package metrics_test import ( "fmt" "runtime" "github.com/VictoriaMetrics/metrics" ) func ExampleGauge() { // Define a gauge exporting the number of goroutines. var g = metrics.NewGauge(`goroutines_count`, func() float64 { return float64(runtime.NumGoroutine()) }) // Obtain gauge value. fmt.Println(g.Get()) } func ExampleGauge_vec() { for i := 0; i < 3; i++ { // Dynamically construct metric name and pass it to GetOrCreateGauge. name := fmt.Sprintf(`metric{label1=%q, label2="%d"}`, "value1", i) iLocal := i metrics.GetOrCreateGauge(name, func() float64 { return float64(iLocal + 1) }) } // Read counter values. for i := 0; i < 3; i++ { name := fmt.Sprintf(`metric{label1=%q, label2="%d"}`, "value1", i) n := metrics.GetOrCreateGauge(name, func() float64 { return 0 }).Get() fmt.Println(n) } // Output: // 1 // 2 // 3 } golang-github-victoriametrics-metrics-1.18.1+ds/gauge_test.go000066400000000000000000000023661414325366000242750ustar00rootroot00000000000000package metrics import ( "fmt" "sync" "testing" ) func TestGaugeError(t *testing.T) { expectPanic(t, "NewGauge_nil_callback", func() { NewGauge("NewGauge_nil_callback", nil) }) expectPanic(t, "GetOrCreateGauge_nil_callback", func() { GetOrCreateGauge("GetOrCreateGauge_nil_callback", nil) }) } func TestGaugeSerial(t *testing.T) { name := "GaugeSerial" n := 1.23 var nLock sync.Mutex g := NewGauge(name, func() float64 { nLock.Lock() defer nLock.Unlock() n++ return n }) for i := 0; i < 10; i++ { if nn := g.Get(); nn != n { t.Fatalf("unexpected gauge value; got %v; want %v", nn, n) } } // Verify marshalTo testMarshalTo(t, g, "foobar", "foobar 12.23\n") // Verify big numbers marshaling n = 1234567899 testMarshalTo(t, g, "prefix", "prefix 1234567900\n") } func TestGaugeConcurrent(t *testing.T) { name := "GaugeConcurrent" var n int var nLock sync.Mutex g := NewGauge(name, func() float64 { nLock.Lock() defer nLock.Unlock() n++ return float64(n) }) err := testConcurrent(func() error { nPrev := g.Get() for i := 0; i < 10; i++ { if n := g.Get(); n <= nPrev { return fmt.Errorf("gauge value must be greater than %v; got %v", nPrev, n) } } return nil }) if err != nil { t.Fatal(err) } } golang-github-victoriametrics-metrics-1.18.1+ds/go.mod000066400000000000000000000001401414325366000227110ustar00rootroot00000000000000module github.com/VictoriaMetrics/metrics require github.com/valyala/histogram v1.2.0 go 1.12 golang-github-victoriametrics-metrics-1.18.1+ds/go.sum000066400000000000000000000005341414325366000227450ustar00rootroot00000000000000github.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-github-victoriametrics-metrics-1.18.1+ds/go_metrics.go000066400000000000000000000055221414325366000242760ustar00rootroot00000000000000package metrics import ( "fmt" "io" "runtime" "github.com/valyala/histogram" ) func writeGoMetrics(w io.Writer) { 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()) gcPauses := histogram.NewFast() for _, pauseNs := range ms.PauseNs[:] { gcPauses.Update(float64(pauseNs) / 1e9) } phis := []float64{0, 0.25, 0.5, 0.75, 1} quantiles := make([]float64, 0, len(phis)) 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_gomaxprocs %d`+"\n", runtime.GOMAXPROCS(0)) fmt.Fprintf(w, `go_goroutines %d`+"\n", runtime.NumGoroutine()) numThread, _ := runtime.ThreadCreateProfile(nil) fmt.Fprintf(w, `go_threads %d`+"\n", numThread) // Export build details. fmt.Fprintf(w, "go_info{version=%q} 1\n", runtime.Version()) fmt.Fprintf(w, "go_info_ext{compiler=%q, GOARCH=%q, GOOS=%q, GOROOT=%q} 1\n", runtime.Compiler, runtime.GOARCH, runtime.GOOS, runtime.GOROOT()) } golang-github-victoriametrics-metrics-1.18.1+ds/histogram.go000066400000000000000000000141471414325366000241430ustar00rootroot00000000000000package metrics import ( "fmt" "io" "math" "sync" "time" ) const ( e10Min = -9 e10Max = 18 bucketsPerDecimal = 18 decimalBucketsCount = e10Max - e10Min bucketsCount = decimalBucketsCount * bucketsPerDecimal ) var bucketMultiplier = math.Pow(10, 1.0/bucketsPerDecimal) // Histogram is a histogram for non-negative values with automatically created buckets. // // See https://medium.com/@valyala/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350 // // Each bucket contains a counter for values in the given range. // Each non-empty bucket is exposed via the following metric: // // _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 // // 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 ): // // 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 // for all the previous buckets. // // Zero histogram is usable. type Histogram struct { // Mu gurantees synchronous update for all the counters and sum. mu sync.Mutex decimalBuckets [decimalBucketsCount]*[bucketsPerDecimal]uint64 lower uint64 upper uint64 sum float64 } // Reset resets the given histogram. func (h *Histogram) Reset() { h.mu.Lock() for _, db := range h.decimalBuckets[:] { if db == nil { continue } for i := range db[:] { db[i] = 0 } } h.lower = 0 h.upper = 0 h.sum = 0 h.mu.Unlock() } // Update updates h with v. // // Negative values and NaNs are ignored. func (h *Histogram) Update(v float64) { if math.IsNaN(v) || v < 0 { // Skip NaNs and negative values. return } bucketIdx := (math.Log10(v) - e10Min) * bucketsPerDecimal h.mu.Lock() h.sum += v if bucketIdx < 0 { h.lower++ } else if bucketIdx >= bucketsCount { h.upper++ } else { idx := uint(bucketIdx) if bucketIdx == float64(idx) && idx > 0 { // Edge case for 10^n values, which must go to the lower bucket // according to Prometheus logic for `le`-based histograms. idx-- } decimalBucketIdx := idx / bucketsPerDecimal offset := idx % bucketsPerDecimal db := h.decimalBuckets[decimalBucketIdx] if db == nil { var b [bucketsPerDecimal]uint64 db = &b h.decimalBuckets[decimalBucketIdx] = db } db[offset]++ } h.mu.Unlock() } // VisitNonZeroBuckets calls f for all buckets with non-zero counters. // // vmrange contains "..." string with bucket bounds. The lower bound // isn't included in the bucket, while the upper bound is included. // This is required to be compatible with Prometheus-style histogram buckets // with `le` (less or equal) labels. func (h *Histogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) { h.mu.Lock() if h.lower > 0 { f(lowerBucketRange, h.lower) } for decimalBucketIdx, db := range h.decimalBuckets[:] { if db == nil { continue } for offset, count := range db[:] { if count > 0 { bucketIdx := decimalBucketIdx*bucketsPerDecimal + offset vmrange := getVMRange(bucketIdx) f(vmrange, count) } } } if h.upper > 0 { f(upperBucketRange, h.upper) } h.mu.Unlock() } // NewHistogram creates and returns new 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. func NewHistogram(name string) *Histogram { return defaultSet.NewHistogram(name) } // GetOrCreateHistogram returns registered histogram with the given name // or creates new histogram 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 NewHistogram instead of GetOrCreateHistogram. func GetOrCreateHistogram(name string) *Histogram { return defaultSet.GetOrCreateHistogram(name) } // UpdateDuration updates request duration based on the given startTime. func (h *Histogram) UpdateDuration(startTime time.Time) { d := time.Since(startTime).Seconds() h.Update(d) } func getVMRange(bucketIdx int) string { bucketRangesOnce.Do(initBucketRanges) return bucketRanges[bucketIdx] } func initBucketRanges() { v := math.Pow10(e10Min) start := fmt.Sprintf("%.3e", v) for i := 0; i < bucketsCount; i++ { v *= bucketMultiplier end := fmt.Sprintf("%.3e", v) bucketRanges[i] = start + "..." + end start = end } } var ( lowerBucketRange = fmt.Sprintf("0...%.3e", math.Pow10(e10Min)) upperBucketRange = fmt.Sprintf("%.3e...+Inf", math.Pow10(e10Max)) bucketRanges [bucketsCount]string bucketRangesOnce sync.Once ) func (h *Histogram) marshalTo(prefix string, w io.Writer) { countTotal := uint64(0) h.VisitNonZeroBuckets(func(vmrange string, count uint64) { tag := fmt.Sprintf("vmrange=%q", vmrange) metricName := addTag(prefix, tag) name, labels := splitMetricName(metricName) fmt.Fprintf(w, "%s_bucket%s %d\n", name, labels, count) countTotal += count }) if countTotal == 0 { return } name, labels := splitMetricName(prefix) sum := h.getSum() 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, countTotal) } func (h *Histogram) getSum() float64 { h.mu.Lock() sum := h.sum h.mu.Unlock() return sum } golang-github-victoriametrics-metrics-1.18.1+ds/histogram_example_test.go000066400000000000000000000012421414325366000267050ustar00rootroot00000000000000package metrics_test import ( "fmt" "time" "github.com/VictoriaMetrics/metrics" ) func ExampleHistogram() { // Define a histogram in global scope. var h = metrics.NewHistogram(`request_duration_seconds{path="/foo/bar"}`) // Update the histogram with the duration of processRequest call. startTime := time.Now() processRequest() h.UpdateDuration(startTime) } 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") response := processRequest() metrics.GetOrCreateHistogram(name).Update(float64(len(response))) } } golang-github-victoriametrics-metrics-1.18.1+ds/histogram_test.go000066400000000000000000000134711414325366000252010ustar00rootroot00000000000000package metrics import ( "bytes" "fmt" "math" "reflect" "strings" "testing" "time" ) func TestGetVMRange(t *testing.T) { f := func(bucketIdx int, vmrangeExpected string) { t.Helper() vmrange := getVMRange(bucketIdx) if vmrange != vmrangeExpected { t.Fatalf("unexpected vmrange for bucketIdx=%d; got %s; want %s", bucketIdx, vmrange, vmrangeExpected) } } f(0, "1.000e-09...1.136e-09") f(1, "1.136e-09...1.292e-09") f(bucketsPerDecimal-1, "8.799e-09...1.000e-08") f(bucketsPerDecimal, "1.000e-08...1.136e-08") f(bucketsPerDecimal*(-e10Min)-1, "8.799e-01...1.000e+00") f(bucketsPerDecimal*(-e10Min), "1.000e+00...1.136e+00") f(bucketsPerDecimal*(e10Max-e10Min)-1, "8.799e+17...1.000e+18") } func TestHistogramSerial(t *testing.T) { name := `TestHistogramSerial` h := NewHistogram(name) // Verify that the histogram is invisible in the output of WritePrometheus when it has no data. var bb bytes.Buffer WritePrometheus(&bb, false) result := bb.String() if strings.Contains(result, name) { t.Fatalf("histogram %s shouldn't be visible in the WritePrometheus output; got\n%s", name, result) } // Write data to histogram for i := 98; i < 218; i++ { h.Update(float64(i)) } // Make sure the histogram prints _bucket on marshalTo call testMarshalTo(t, h, "prefix", `prefix_bucket{vmrange="8.799e+01...1.000e+02"} 3 prefix_bucket{vmrange="1.000e+02...1.136e+02"} 13 prefix_bucket{vmrange="1.136e+02...1.292e+02"} 16 prefix_bucket{vmrange="1.292e+02...1.468e+02"} 17 prefix_bucket{vmrange="1.468e+02...1.668e+02"} 20 prefix_bucket{vmrange="1.668e+02...1.896e+02"} 23 prefix_bucket{vmrange="1.896e+02...2.154e+02"} 26 prefix_bucket{vmrange="2.154e+02...2.448e+02"} 2 prefix_sum 18900 prefix_count 120 `) testMarshalTo(t, h, ` m{foo="bar"}`, ` m_bucket{foo="bar",vmrange="8.799e+01...1.000e+02"} 3 m_bucket{foo="bar",vmrange="1.000e+02...1.136e+02"} 13 m_bucket{foo="bar",vmrange="1.136e+02...1.292e+02"} 16 m_bucket{foo="bar",vmrange="1.292e+02...1.468e+02"} 17 m_bucket{foo="bar",vmrange="1.468e+02...1.668e+02"} 20 m_bucket{foo="bar",vmrange="1.668e+02...1.896e+02"} 23 m_bucket{foo="bar",vmrange="1.896e+02...2.154e+02"} 26 m_bucket{foo="bar",vmrange="2.154e+02...2.448e+02"} 2 m_sum{foo="bar"} 18900 m_count{foo="bar"} 120 `) // Verify Reset h.Reset() bb.Reset() WritePrometheus(&bb, false) result = bb.String() if strings.Contains(result, name) { t.Fatalf("unexpected histogram %s in the WritePrometheus output; got\n%s", name, result) } // Verify supported ranges for e10 := -100; e10 < 100; e10++ { for offset := 0; offset < bucketsPerDecimal; offset++ { m := 1 + math.Pow(bucketMultiplier, float64(offset)) f1 := m * math.Pow10(e10) h.Update(f1) f2 := (m + 0.5*bucketMultiplier) * math.Pow10(e10) h.Update(f2) f3 := (m + 2*bucketMultiplier) * math.Pow10(e10) h.Update(f3) } } h.UpdateDuration(time.Now().Add(-time.Minute)) // Verify edge cases h.Update(0) h.Update(math.Inf(1)) h.Update(math.Inf(-1)) h.Update(math.NaN()) h.Update(-123) // See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1096 h.Update(math.Float64frombits(0x3e112e0be826d695)) // Make sure the histogram becomes visible in the output of WritePrometheus, // since now it contains values. bb.Reset() WritePrometheus(&bb, false) result = bb.String() if !strings.Contains(result, name) { t.Fatalf("missing histogram %s in the WritePrometheus output; got\n%s", name, result) } } func TestHistogramConcurrent(t *testing.T) { name := "HistogramConcurrent" h := NewHistogram(name) err := testConcurrent(func() error { for f := 0.6; f < 1.4; f += 0.1 { h.Update(f) } return nil }) if err != nil { t.Fatal(err) } testMarshalTo(t, h, "prefix", `prefix_bucket{vmrange="5.995e-01...6.813e-01"} 5 prefix_bucket{vmrange="6.813e-01...7.743e-01"} 5 prefix_bucket{vmrange="7.743e-01...8.799e-01"} 5 prefix_bucket{vmrange="8.799e-01...1.000e+00"} 10 prefix_bucket{vmrange="1.000e+00...1.136e+00"} 5 prefix_bucket{vmrange="1.136e+00...1.292e+00"} 5 prefix_bucket{vmrange="1.292e+00...1.468e+00"} 5 prefix_sum 38 prefix_count 40 `) var labels []string var counts []uint64 h.VisitNonZeroBuckets(func(label string, count uint64) { labels = append(labels, label) counts = append(counts, count) }) labelsExpected := []string{ "5.995e-01...6.813e-01", "6.813e-01...7.743e-01", "7.743e-01...8.799e-01", "8.799e-01...1.000e+00", "1.000e+00...1.136e+00", "1.136e+00...1.292e+00", "1.292e+00...1.468e+00", } if !reflect.DeepEqual(labels, labelsExpected) { t.Fatalf("unexpected labels; got %v; want %v", labels, labelsExpected) } countsExpected := []uint64{5, 5, 5, 10, 5, 5, 5} if !reflect.DeepEqual(counts, countsExpected) { t.Fatalf("unexpected counts; got %v; want %v", counts, countsExpected) } } func TestHistogramWithTags(t *testing.T) { name := `TestHistogram{tag="foo"}` h := NewHistogram(name) h.Update(123) var bb bytes.Buffer WritePrometheus(&bb, false) result := bb.String() namePrefixWithTag := `TestHistogram_bucket{tag="foo",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 { t.Fatal(err) } } func TestGetOrCreateHistogramConcurrent(t *testing.T) { name := "GetOrCreateHistogramConcurrent" err := testConcurrent(func() error { return testGetOrCreateHistogram(name) }) if err != nil { t.Fatal(err) } } func testGetOrCreateHistogram(name string) error { h1 := GetOrCreateHistogram(name) for i := 0; i < 10; i++ { h2 := GetOrCreateHistogram(name) if h1 != h2 { return fmt.Errorf("unexpected histogram returned; got %p; want %p", h2, h1) } } return nil } golang-github-victoriametrics-metrics-1.18.1+ds/histogram_timing_test.go000066400000000000000000000004111414325366000265360ustar00rootroot00000000000000package metrics import ( "testing" ) func BenchmarkHistogramUpdate(b *testing.B) { h := GetOrCreateHistogram("BenchmarkHistogramUpdate") b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { h.Update(float64(i)) i++ } }) } golang-github-victoriametrics-metrics-1.18.1+ds/metrics.go000066400000000000000000000115161414325366000236110ustar00rootroot00000000000000// Package metrics implements Prometheus-compatible metrics for applications. // // This package is lightweight alternative to https://github.com/prometheus/client_golang // with simpler API and smaller dependencies. // // 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. // // The package has been extracted from https://victoriametrics.com/ package metrics import ( "io" ) type namedMetric struct { name string metric metric } type metric interface { marshalTo(prefix string, w io.Writer) } var defaultSet = NewSet() // WritePrometheus writes all the registered metrics in Prometheus format to w. // // 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) // }) // func WritePrometheus(w io.Writer, exposeProcessMetrics bool) { defaultSet.WritePrometheus(w) if exposeProcessMetrics { WriteProcessMetrics(w) } } // WriteProcessMetrics writes additional process metrics in Prometheus format to w. // // 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 // // 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) // }) // // See also WrteFDMetrics. func WriteProcessMetrics(w io.Writer) { writeGoMetrics(w) writeProcessMetrics(w) } // WriteFDMetrics writes `process_max_fds` and `process_open_fds` metrics to w. func WriteFDMetrics(w io.Writer) { writeFDMetrics(w) } // UnregisterMetric removes metric with the given name from default set. func UnregisterMetric(name string) bool { return defaultSet.UnregisterMetric(name) } golang-github-victoriametrics-metrics-1.18.1+ds/metrics_example_test.go000066400000000000000000000005001414325366000263520ustar00rootroot00000000000000package metrics_test import ( "net/http" "github.com/VictoriaMetrics/metrics" ) func ExampleWritePrometheus() { // Export all the registered metrics in Prometheus format at `/metrics` http path. http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { metrics.WritePrometheus(w, true) }) } golang-github-victoriametrics-metrics-1.18.1+ds/metrics_test.go000066400000000000000000000076741414325366000246620ustar00rootroot00000000000000package metrics import ( "bytes" "fmt" "testing" "time" ) func TestInvalidName(t *testing.T) { f := func(name string) { t.Helper() expectPanic(t, fmt.Sprintf("NewCounter(%q)", name), func() { NewCounter(name) }) expectPanic(t, fmt.Sprintf("NewGauge(%q)", name), func() { NewGauge(name, func() float64 { return 0 }) }) expectPanic(t, fmt.Sprintf("NewSummary(%q)", name), func() { NewSummary(name) }) expectPanic(t, fmt.Sprintf("GetOrCreateCounter(%q)", name), func() { GetOrCreateCounter(name) }) expectPanic(t, fmt.Sprintf("GetOrCreateGauge(%q)", name), func() { GetOrCreateGauge(name, func() float64 { return 0 }) }) expectPanic(t, fmt.Sprintf("GetOrCreateSummary(%q)", name), func() { GetOrCreateSummary(name) }) expectPanic(t, fmt.Sprintf("GetOrCreateHistogram(%q)", name), func() { GetOrCreateHistogram(name) }) } f("") f("foo{") f("foo}") f("foo{bar") f("foo{bar=") f(`foo{bar="`) f(`foo{bar="baz`) f(`foo{bar="baz"`) f(`foo{bar="baz",`) f(`foo{bar="baz",}`) } func TestDoubleRegister(t *testing.T) { t.Run("NewCounter", func(t *testing.T) { name := "NewCounterDoubleRegister" NewCounter(name) expectPanic(t, name, func() { NewCounter(name) }) }) t.Run("NewGauge", func(t *testing.T) { name := "NewGaugeDoubleRegister" NewGauge(name, func() float64 { return 0 }) expectPanic(t, name, func() { NewGauge(name, func() float64 { return 0 }) }) }) t.Run("NewSummary", func(t *testing.T) { name := "NewSummaryDoubleRegister" NewSummary(name) expectPanic(t, name, func() { NewSummary(name) }) }) t.Run("NewHistogram", func(t *testing.T) { name := "NewHistogramDoubleRegister" NewHistogram(name) expectPanic(t, name, func() { NewSummary(name) }) }) } func TestGetOrCreateNotCounter(t *testing.T) { name := "GetOrCreateNotCounter" NewSummary(name) expectPanic(t, name, func() { GetOrCreateCounter(name) }) } func TestGetOrCreateNotGauge(t *testing.T) { name := "GetOrCreateNotGauge" NewCounter(name) expectPanic(t, name, func() { GetOrCreateGauge(name, func() float64 { return 0 }) }) } func TestGetOrCreateNotSummary(t *testing.T) { name := "GetOrCreateNotSummary" NewCounter(name) expectPanic(t, name, func() { GetOrCreateSummary(name) }) } func TestGetOrCreateNotHistogram(t *testing.T) { name := "GetOrCreateNotHistogram" NewCounter(name) expectPanic(t, name, func() { GetOrCreateHistogram(name) }) } func TestWritePrometheusSerial(t *testing.T) { if err := testWritePrometheus(); err != nil { t.Fatal(err) } } func TestWritePrometheusConcurrent(t *testing.T) { if err := testConcurrent(testWritePrometheus); err != nil { t.Fatal(err) } } func testWritePrometheus() error { var bb bytes.Buffer WritePrometheus(&bb, false) resultWithoutProcessMetrics := bb.String() bb.Reset() WritePrometheus(&bb, true) resultWithProcessMetrics := bb.String() if len(resultWithProcessMetrics) <= len(resultWithoutProcessMetrics) { return fmt.Errorf("result with process metrics must contain more data than the result without process metrics; got\n%q\nvs\n%q", resultWithProcessMetrics, resultWithoutProcessMetrics) } return nil } func expectPanic(t *testing.T, context string, f func()) { t.Helper() defer func() { t.Helper() if r := recover(); r == nil { t.Fatalf("expecting panic in %s", context) } }() f() } func testConcurrent(f func() error) error { const concurrency = 5 resultsCh := make(chan error, concurrency) for i := 0; i < concurrency; i++ { go func() { resultsCh <- f() }() } for i := 0; i < concurrency; i++ { select { case err := <-resultsCh: if err != nil { return fmt.Errorf("unexpected error: %s", err) } case <-time.After(time.Second * 5): return fmt.Errorf("timeout") } } return nil } func testMarshalTo(t *testing.T, m metric, prefix, resultExpected string) { t.Helper() var bb bytes.Buffer m.marshalTo(prefix, &bb) result := bb.String() if result != resultExpected { t.Fatalf("unexpected marshaled metric;\ngot\n%q\nwant\n%q", result, resultExpected) } } golang-github-victoriametrics-metrics-1.18.1+ds/process_metrics_linux.go000066400000000000000000000164441414325366000265730ustar00rootroot00000000000000package metrics import ( "bytes" "fmt" "io" "io/ioutil" "log" "os" "strconv" "strings" "time" ) // See https://github.com/prometheus/procfs/blob/a4ac0826abceb44c40fc71daed2b301db498b93e/proc_stat.go#L40 . const userHZ = 100 // See http://man7.org/linux/man-pages/man5/proc.5.html type procStat struct { State byte Ppid int Pgrp int Session int TtyNr int Tpgid int Flags uint Minflt uint Cminflt uint Majflt uint Cmajflt uint Utime uint Stime uint Cutime int Cstime int Priority int Nice int NumThreads int ItrealValue int Starttime uint64 Vsize uint Rss int } 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) 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) return } data = data[n+2:] var p procStat bb := bytes.NewBuffer(data) _, err = fmt.Fscanf(bb, "%c %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", &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) return } // It is expensive obtaining `process_open_fds` when big number of file descriptors is opened, // so don't do it here. // See writeFDMetrics instead. 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) writeProcessMemMetrics(w) writeIOMetrics(w) } 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) } getInt := func(s string) int64 { n := strings.IndexByte(s, ' ') if n < 0 { log.Printf("ERROR: 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) return 0 } return v } var rchar, wchar, syscr, syscw, readBytes, writeBytes int64 lines := strings.Split(string(data), "\n") for _, s := range lines { s = strings.TrimSpace(s) switch { case strings.HasPrefix(s, "rchar: "): rchar = getInt(s) case strings.HasPrefix(s, "wchar: "): wchar = getInt(s) case strings.HasPrefix(s, "syscr: "): syscr = getInt(s) case strings.HasPrefix(s, "syscw: "): syscw = getInt(s) case strings.HasPrefix(s, "read_bytes: "): readBytes = getInt(s) case strings.HasPrefix(s, "write_bytes: "): 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) } var startTimeSeconds = time.Now().Unix() // writeFDMetrics writes process_max_fds and process_open_fds metrics to w. 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) return } maxOpenFDs, err := getMaxFilesLimit("/proc/self/limits") if err != nil { log.Printf("ERROR: 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) } func getOpenFDsCount(path string) (uint64, error) { f, err := os.Open(path) if err != nil { return 0, err } defer f.Close() var totalOpenFDs uint64 for { names, err := f.Readdirnames(512) if err == io.EOF { break } if err != nil { return 0, fmt.Errorf("unexpected error at Readdirnames: %s", err) } totalOpenFDs += uint64(len(names)) } return totalOpenFDs, nil } func getMaxFilesLimit(path string) (uint64, error) { data, err := ioutil.ReadFile(path) if err != nil { return 0, err } lines := strings.Split(string(data), "\n") const prefix = "Max open files" for _, s := range lines { if !strings.HasPrefix(s, prefix) { continue } text := strings.TrimSpace(s[len(prefix):]) // Extract soft limit. n := strings.IndexByte(text, ' ') if n < 0 { return 0, fmt.Errorf("cannot extract soft limit from %q", s) } text = text[:n] if text == "unlimited" { return 1<<64 - 1, nil } limit, err := strconv.ParseUint(text, 10, 64) if err != nil { return 0, fmt.Errorf("cannot parse soft limit from %q: %s", s, err) } return limit, nil } return 0, fmt.Errorf("cannot find max open files limit") } // https://man7.org/linux/man-pages/man5/procfs.5.html type memStats struct { vmPeak uint64 rssPeak uint64 rssAnon uint64 rssFile uint64 rssShmem uint64 } func writeProcessMemMetrics(w io.Writer) { ms, err := getMemStats("/proc/self/status") if err != nil { log.Printf("ERROR: 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) } func getMemStats(path string) (*memStats, error) { data, err := ioutil.ReadFile(path) if err != nil { return nil, err } var ms memStats lines := strings.Split(string(data), "\n") for _, s := range lines { if !strings.HasPrefix(s, "Vm") && !strings.HasPrefix(s, "Rss") { continue } // Extract key value. line := strings.Fields(s) if len(line) != 3 { return nil, fmt.Errorf("unexpected number of fields found in %q; got %d; want %d", s, len(line), 3) } memStatName := line[0] memStatValue := line[1] value, err := strconv.ParseUint(memStatValue, 10, 64) if err != nil { return nil, fmt.Errorf("cannot parse number from %q: %w", s, err) } if line[2] != "kB" { return nil, fmt.Errorf("expecting kB value in %q; got %q", s, line[2]) } value *= 1024 switch memStatName { case "VmPeak:": ms.vmPeak = value case "VmHWM:": ms.rssPeak = value case "RssAnon:": ms.rssAnon = value case "RssFile:": ms.rssFile = value case "RssShmem:": ms.rssShmem = value } } return &ms, nil } golang-github-victoriametrics-metrics-1.18.1+ds/process_metrics_linux_test.go000066400000000000000000000026241414325366000276250ustar00rootroot00000000000000package metrics import "testing" func TestGetMaxFilesLimit(t *testing.T) { f := func(want uint64, path string, wantErr bool) { t.Helper() got, err := getMaxFilesLimit(path) if err != nil && !wantErr { t.Fatalf("unexpected error: %v", err) } if got != want { t.Fatalf("unexpected result: %d, want: %d at getMaxFilesLimit", got, want) } } f(1024, "testdata/limits", false) f(0, "testdata/bad_path", true) f(0, "testdata/limits_bad", true) } func TestGetOpenFDsCount(t *testing.T) { f := func(want uint64, path string, wantErr bool) { t.Helper() got, err := getOpenFDsCount(path) if (err != nil && !wantErr) || (err == nil && wantErr) { t.Fatalf("unexpected error: %v", err) } if got != want { t.Fatalf("unexpected result: %d, want: %d at getOpenFDsCount", got, want) } } f(5, "testdata/fd/", false) f(0, "testdata/fd/0", true) f(0, "testdata/limits", true) } func TestGetMemStats(t *testing.T) { f := func(want memStats, path string, wantErr bool) { t.Helper() got, err := getMemStats(path) if (err != nil && !wantErr) || (err == nil && wantErr) { t.Fatalf("unexpected error: %v", err) } if got != nil && *got != want { t.Fatalf("unexpected result: %d, want: %d at getMemStats", *got, want) } } f(memStats{vmPeak: 2130489344, rssPeak: 200679424, rssAnon: 121602048, rssFile: 11362304}, "testdata/status", false) f(memStats{}, "testdata/status_bad", true) } golang-github-victoriametrics-metrics-1.18.1+ds/process_metrics_other.go000066400000000000000000000002641414325366000265460ustar00rootroot00000000000000// +build !linux package metrics import ( "io" ) func writeProcessMetrics(w io.Writer) { // TODO: implement it } func writeFDMetrics(w io.Writer) { // TODO: implement it. } golang-github-victoriametrics-metrics-1.18.1+ds/set.go000066400000000000000000000325031414325366000227350ustar00rootroot00000000000000package metrics import ( "bytes" "fmt" "io" "sort" "sync" "time" ) // Set is a set of metrics. // // Metrics belonging to a set are exported separately from global metrics. // // Set.WritePrometheus must be called for exporting metrics from the set. type Set struct { mu sync.Mutex a []*namedMetric m map[string]*namedMetric summaries []*Summary } // NewSet creates new set of metrics. func NewSet() *Set { return &Set{ m: make(map[string]*namedMetric), } } // WritePrometheus writes all the metrics from s to w in Prometheus format. func (s *Set) WritePrometheus(w io.Writer) { // Collect all the metrics in in-memory buffer in order to prevent from long locking due to slow w. var bb bytes.Buffer lessFunc := func(i, j int) bool { return s.a[i].name < s.a[j].name } s.mu.Lock() for _, sm := range s.summaries { sm.updateQuantiles() } if !sort.SliceIsSorted(s.a, lessFunc) { sort.Slice(s.a, lessFunc) } sa := append([]*namedMetric(nil), s.a...) 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. for _, nm := range sa { nm.metric.marshalTo(nm.name, &bb) } w.Write(bb.Bytes()) } // NewHistogram creates and returns new histogram in s 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. func (s *Set) NewHistogram(name string) *Histogram { h := &Histogram{} s.registerMetric(name, h) return h } // GetOrCreateHistogram returns registered 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 NewHistogram instead of GetOrCreateHistogram. func (s *Set) GetOrCreateHistogram(name string) *Histogram { 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: &Histogram{}, } 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.(*Histogram) if !ok { panic(fmt.Errorf("BUG: metric %q isn't a Histogram. 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"} // // The returned counter is safe to use from concurrent goroutines. func (s *Set) NewCounter(name string) *Counter { c := &Counter{} s.registerMetric(name, c) return c } // GetOrCreateCounter returns registered counter in s with the given name // or creates new counter if s doesn't contain counter 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 counter is safe to use from concurrent goroutines. // // Performance tip: prefer NewCounter instead of GetOrCreateCounter. func (s *Set) GetOrCreateCounter(name string) *Counter { s.mu.Lock() nm := s.m[name] s.mu.Unlock() if nm == nil { // Slow path - create and register missing counter. if err := validateMetric(name); err != nil { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } nmNew := &namedMetric{ name: name, metric: &Counter{}, } 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() } c, ok := nm.metric.(*Counter) if !ok { panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric)) } return c } // NewFloatCounter registers and returns new FloatCounter 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"} // // The returned FloatCounter is safe to use from concurrent goroutines. func (s *Set) NewFloatCounter(name string) *FloatCounter { c := &FloatCounter{} s.registerMetric(name, c) return c } // GetOrCreateFloatCounter returns registered FloatCounter in s with the given name // or creates new FloatCounter if s doesn't contain FloatCounter 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 FloatCounter is safe to use from concurrent goroutines. // // Performance tip: prefer NewFloatCounter instead of GetOrCreateFloatCounter. func (s *Set) GetOrCreateFloatCounter(name string) *FloatCounter { s.mu.Lock() nm := s.m[name] s.mu.Unlock() if nm == nil { // Slow path - create and register missing counter. if err := validateMetric(name); err != nil { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } nmNew := &namedMetric{ name: name, metric: &FloatCounter{}, } 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() } c, ok := nm.metric.(*FloatCounter) if !ok { panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric)) } return c } // NewGauge registers and returns gauge with the given name in s, 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"} // // 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, } s.registerMetric(name, g) return g } // GetOrCreateGauge returns registered gauge with the given name in s // or creates new gauge if s doesn't contain gauge 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 gauge is safe to use from concurrent goroutines. // // Performance tip: prefer NewGauge instead of GetOrCreateGauge. func (s *Set) GetOrCreateGauge(name string, f func() float64) *Gauge { s.mu.Lock() nm := s.m[name] 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 { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } nmNew := &namedMetric{ name: name, metric: &Gauge{ f: f, }, } 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() } g, ok := nm.metric.(*Gauge) if !ok { panic(fmt.Errorf("BUG: metric %q isn't a Gauge. It is %T", name, nm.metric)) } return g } // NewSummary creates and returns new summary with the given name in s. // // name must be valid Prometheus-compatible metric with possible labels. // For instance, // // * 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 { return s.NewSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles) } // NewSummaryExt creates and returns new summary in s with the given name, // window and quantiles. // // name must be valid Prometheus-compatible metric with possible labels. // For instance, // // * 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 { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } sm := newSummary(window, quantiles) s.mu.Lock() // defer will unlock in case of panic // checks in tests defer s.mu.Unlock() s.mustRegisterLocked(name, sm) registerSummaryLocked(sm) s.registerSummaryQuantilesLocked(name, sm) s.summaries = append(s.summaries, sm) return sm } // GetOrCreateSummary returns registered summary with the given name in s // or creates new summary if s doesn't contain summary 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 summary is safe to use from concurrent goroutines. // // Performance tip: prefer NewSummary instead of GetOrCreateSummary. func (s *Set) GetOrCreateSummary(name string) *Summary { return s.GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles) } // GetOrCreateSummaryExt returns registered summary with the given name, // window and quantiles in s or creates new summary if s doesn't // contain summary 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 summary is safe to use from concurrent goroutines. // // Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt. func (s *Set) GetOrCreateSummaryExt(name string, window time.Duration, quantiles []float64) *Summary { s.mu.Lock() nm := s.m[name] s.mu.Unlock() if nm == nil { // Slow path - create and register missing summary. if err := validateMetric(name); err != nil { panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err)) } sm := newSummary(window, quantiles) nmNew := &namedMetric{ name: name, metric: sm, } s.mu.Lock() nm = s.m[name] if nm == nil { nm = nmNew s.m[name] = nm s.a = append(s.a, nm) registerSummaryLocked(sm) s.registerSummaryQuantilesLocked(name, sm) } s.summaries = append(s.summaries, sm) s.mu.Unlock() } sm, ok := nm.metric.(*Summary) if !ok { panic(fmt.Errorf("BUG: metric %q isn't a Summary. It is %T", name, nm.metric)) } if sm.window != window { panic(fmt.Errorf("BUG: invalid window requested for the summary %q; requested %s; need %s", name, window, sm.window)) } if !isEqualQuantiles(sm.quantiles, quantiles) { panic(fmt.Errorf("BUG: invalid quantiles requested from the summary %q; requested %v; need %v", name, quantiles, sm.quantiles)) } return sm } func (s *Set) registerSummaryQuantilesLocked(name string, sm *Summary) { for i, q := range sm.quantiles { quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q)) qv := &quantileValue{ sm: sm, idx: i, } s.mustRegisterLocked(quantileValueName, qv) } } func (s *Set) registerMetric(name string, m metric) { 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) } // 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) { nm, ok := s.m[name] if !ok { nm = &namedMetric{ name: name, metric: m, } s.m[name] = nm s.a = append(s.a, nm) } if ok { panic(fmt.Errorf("BUG: metric %q is already registered", name)) } } // UnregisterMetric removes metric with the given name from s. // // True is returned if the metric has been removed. // False is returned if the given metric is missing in s. func (s *Set) UnregisterMetric(name string) bool { s.mu.Lock() defer s.mu.Unlock() nm, ok := s.m[name] if !ok { return false } m := nm.metric delete(s.m, name) deleteFromList := func(metricName string) { for i, nm := range s.a { if nm.name == metricName { s.a = append(s.a[:i], s.a[i+1:]...) return } } panic(fmt.Errorf("BUG: cannot find metric %q in the list of registered metrics", name)) } // remove metric from s.a deleteFromList(name) sm, ok := m.(*Summary) if !ok { // There is no need in cleaning up summary. return true } // cleanup registry from per-quantile metrics for _, q := range sm.quantiles { quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q)) delete(s.m, quantileValueName) deleteFromList(quantileValueName) } // Remove sm from s.summaries found := false for i, xsm := range s.summaries { if xsm == sm { s.summaries = append(s.summaries[:i], s.summaries[i+1:]...) found = true break } } if !found { panic(fmt.Errorf("BUG: cannot find summary %q in the list of registered summaries", name)) } unregisterSummary(sm) return true } // ListMetricNames returns a list of all the metrics in s. func (s *Set) ListMetricNames() []string { s.mu.Lock() defer s.mu.Unlock() var list []string for name := range s.m { list = append(list, name) } return list } golang-github-victoriametrics-metrics-1.18.1+ds/set_example_test.go000066400000000000000000000007261414325366000255110ustar00rootroot00000000000000package metrics_test import ( "bytes" "fmt" "github.com/VictoriaMetrics/metrics" ) func ExampleSet() { // Create a set with a counter s := metrics.NewSet() sc := s.NewCounter("set_counter") sc.Inc() s.NewGauge(`set_gauge{foo="bar"}`, func() float64 { return 42 }) // Dump metrics from s. var bb bytes.Buffer s.WritePrometheus(&bb) fmt.Printf("set metrics:\n%s\n", bb.String()) // Output: // set metrics: // set_counter 1 // set_gauge{foo="bar"} 42 } golang-github-victoriametrics-metrics-1.18.1+ds/set_test.go000066400000000000000000000071651414325366000240020ustar00rootroot00000000000000package metrics import ( "fmt" "sync" "testing" "time" ) func TestNewSet(t *testing.T) { var ss []*Set for i := 0; i < 10; i++ { s := NewSet() ss = append(ss, s) } for i := 0; i < 10; i++ { s := ss[i] for j := 0; j < 10; j++ { c := s.NewCounter(fmt.Sprintf("counter_%d", j)) c.Inc() if n := c.Get(); n != 1 { t.Fatalf("unexpected counter value; got %d; want %d", n, 1) } g := s.NewGauge(fmt.Sprintf("gauge_%d", j), func() float64 { return 123 }) if v := g.Get(); v != 123 { t.Fatalf("unexpected gauge value; got %v; want %v", v, 123) } sm := s.NewSummary(fmt.Sprintf("summary_%d", j)) if sm == nil { t.Fatalf("NewSummary returned nil") } h := s.NewHistogram(fmt.Sprintf("histogram_%d", j)) if h == nil { t.Fatalf("NewHistogram returned nil") } } } } func TestSetListMetricNames(t *testing.T) { s := NewSet() expect := []string{"cnt1", "cnt2", "cnt3"} // Initialize a few counters for _, n := range expect { c := s.NewCounter(n) c.Inc() } list := s.ListMetricNames() if len(list) != len(expect) { t.Fatalf("Metrics count is wrong for listing") } for _, e := range expect { found := false for _, n := range list { if e == n { found = true } } if !found { t.Fatalf("Metric %s not found in listing", e) } } } func TestSetUnregisterMetric(t *testing.T) { s := NewSet() const cName, smName = "counter_1", "summary_1" // Initialize a few metrics c := s.NewCounter(cName) c.Inc() sm := s.NewSummary(smName) sm.Update(1) // Unregister existing metrics if !s.UnregisterMetric(cName) { t.Fatalf("UnregisterMetric(%s) must return true", cName) } if !s.UnregisterMetric(smName) { t.Fatalf("UnregisterMetric(%s) must return true", smName) } // Unregister twice must return false if s.UnregisterMetric(cName) { t.Fatalf("UnregisterMetric(%s) must return false on unregistered metric", cName) } if s.UnregisterMetric(smName) { t.Fatalf("UnregisterMetric(%s) must return false on unregistered metric", smName) } // verify that registry is empty if len(s.m) != 0 { t.Fatalf("expected metrics map to be empty; got %d elements", len(s.m)) } if len(s.a) != 0 { t.Fatalf("expected metrics list to be empty; got %d elements", len(s.a)) } // Validate metrics are removed ok := false for _, n := range s.ListMetricNames() { if n == cName || n == smName { ok = true } } if ok { t.Fatalf("Metric counter_1 and summary_1 must not be listed anymore after unregister") } // re-register with the same names supposed // to be successful s.NewCounter(cName).Inc() s.NewSummary(smName).Update(float64(1)) } // TestRegisterUnregister tests concurrent access to // metrics during registering and unregistering. // Should be tested specifically with `-race` enabled. func TestRegisterUnregister(t *testing.T) { const ( workers = 16 iterations = 1e3 ) wg := sync.WaitGroup{} wg.Add(workers) for n := 0; n < workers; n++ { go func() { defer wg.Done() now := time.Now() for i := 0; i < iterations; i++ { iteration := i % 5 counter := fmt.Sprintf(`counter{iteration="%d"}`, iteration) GetOrCreateCounter(counter).Add(i) UnregisterMetric(counter) histogram := fmt.Sprintf(`histogram{iteration="%d"}`, iteration) GetOrCreateHistogram(histogram).UpdateDuration(now) UnregisterMetric(histogram) gauge := fmt.Sprintf(`gauge{iteration="%d"}`, iteration) GetOrCreateGauge(gauge, func() float64 { return 1 }) UnregisterMetric(gauge) summary := fmt.Sprintf(`summary{iteration="%d"}`, iteration) GetOrCreateSummary(summary).Update(float64(i)) UnregisterMetric(summary) } }() } wg.Wait() } golang-github-victoriametrics-metrics-1.18.1+ds/summary.go000066400000000000000000000140111414325366000236310ustar00rootroot00000000000000package metrics import ( "fmt" "io" "math" "strings" "sync" "time" "github.com/valyala/histogram" ) const defaultSummaryWindow = 5 * time.Minute var defaultSummaryQuantiles = []float64{0.5, 0.9, 0.97, 0.99, 1} // Summary implements summary. type Summary struct { mu sync.Mutex curr *histogram.Fast next *histogram.Fast quantiles []float64 quantileValues []float64 sum float64 count uint64 window time.Duration } // NewSummary creates and returns new summary 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 summary is safe to use from concurrent goroutines. func NewSummary(name string) *Summary { return defaultSet.NewSummary(name) } // NewSummaryExt creates and returns new summary with the given name, // window and quantiles. // // name must be valid Prometheus-compatible metric with possible labels. // For instance, // // * 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 { return defaultSet.NewSummaryExt(name, window, quantiles) } func newSummary(window time.Duration, quantiles []float64) *Summary { // Make a copy of quantiles in order to prevent from their modification by the caller. quantiles = append([]float64{}, quantiles...) validateQuantiles(quantiles) sm := &Summary{ curr: histogram.NewFast(), next: histogram.NewFast(), quantiles: quantiles, quantileValues: make([]float64, len(quantiles)), window: window, } return sm } func validateQuantiles(quantiles []float64) { for _, q := range quantiles { if q < 0 || q > 1 { panic(fmt.Errorf("BUG: quantile must be in the range [0..1]; got %v", q)) } } } // Update updates the summary. func (sm *Summary) Update(v float64) { sm.mu.Lock() sm.curr.Update(v) sm.next.Update(v) sm.sum += v sm.count++ sm.mu.Unlock() } // UpdateDuration updates request duration based on the given startTime. func (sm *Summary) UpdateDuration(startTime time.Time) { d := time.Since(startTime).Seconds() sm.Update(d) } func (sm *Summary) marshalTo(prefix string, w io.Writer) { // Marshal only *_sum and *_count values. // Quantile values should be already updated by the caller via sm.updateQuantiles() call. // sm.quantileValues will be marshaled later via quantileValue.marshalTo. sm.mu.Lock() sum := sm.sum count := sm.count sm.mu.Unlock() if count > 0 { name, filters := splitMetricName(prefix) if float64(int64(sum)) == sum { // Marshal integer sum without scientific notation fmt.Fprintf(w, "%s_sum%s %d\n", name, filters, int64(sum)) } else { fmt.Fprintf(w, "%s_sum%s %g\n", name, filters, sum) } fmt.Fprintf(w, "%s_count%s %d\n", name, filters, count) } } func splitMetricName(name string) (string, string) { n := strings.IndexByte(name, '{') if n < 0 { return name, "" } return name[:n], name[n:] } func (sm *Summary) updateQuantiles() { sm.mu.Lock() sm.quantileValues = sm.curr.Quantiles(sm.quantileValues[:0], sm.quantiles) sm.mu.Unlock() } // GetOrCreateSummary returns registered summary with the given name // or creates new summary if the registry doesn't contain summary 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 summary is safe to use from concurrent goroutines. // // Performance tip: prefer NewSummary instead of GetOrCreateSummary. func GetOrCreateSummary(name string) *Summary { return defaultSet.GetOrCreateSummary(name) } // GetOrCreateSummaryExt returns registered summary with the given name, // window and quantiles or creates new summary if the registry doesn't // contain summary 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 summary is safe to use from concurrent goroutines. // // Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt. func GetOrCreateSummaryExt(name string, window time.Duration, quantiles []float64) *Summary { return defaultSet.GetOrCreateSummaryExt(name, window, quantiles) } func isEqualQuantiles(a, b []float64) bool { // Do not use relfect.DeepEqual, since it is slower than the direct comparison. if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } type quantileValue struct { sm *Summary idx int } func (qv *quantileValue) marshalTo(prefix string, w io.Writer) { qv.sm.mu.Lock() v := qv.sm.quantileValues[qv.idx] qv.sm.mu.Unlock() if !math.IsNaN(v) { fmt.Fprintf(w, "%s %g\n", prefix, v) } } 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) } func registerSummaryLocked(sm *Summary) { window := sm.window summariesLock.Lock() summaries[window] = append(summaries[window], sm) if len(summaries[window]) == 1 { go summariesSwapCron(window) } summariesLock.Unlock() } func unregisterSummary(sm *Summary) { window := sm.window summariesLock.Lock() sms := summaries[window] found := false for i, xsm := range sms { if xsm == sm { sms = append(sms[:i], sms[i+1:]...) found = true break } } if !found { panic(fmt.Errorf("BUG: cannot find registered summary %p", sm)) } summaries[window] = sms summariesLock.Unlock() } func summariesSwapCron(window time.Duration) { for { time.Sleep(window / 2) summariesLock.Lock() for _, sm := range summaries[window] { sm.mu.Lock() tmp := sm.curr sm.curr = sm.next sm.next = tmp sm.next.Reset() sm.mu.Unlock() } summariesLock.Unlock() } } var ( summaries = map[time.Duration][]*Summary{} summariesLock sync.Mutex ) golang-github-victoriametrics-metrics-1.18.1+ds/summary_example_test.go000066400000000000000000000013071414325366000264070ustar00rootroot00000000000000package metrics_test import ( "fmt" "time" "github.com/VictoriaMetrics/metrics" ) func ExampleSummary() { // Define a summary in global scope. var s = metrics.NewSummary(`request_duration_seconds{path="/foo/bar"}`) // Update the summary with the duration of processRequest call. startTime := time.Now() processRequest() s.UpdateDuration(startTime) } func ExampleSummary_vec() { for i := 0; i < 3; i++ { // Dynamically construct metric name and pass it to GetOrCreateSummary. name := fmt.Sprintf(`response_size_bytes{path=%q}`, "/foo/bar") response := processRequest() metrics.GetOrCreateSummary(name).Update(float64(len(response))) } } func processRequest() string { return "foobar" } golang-github-victoriametrics-metrics-1.18.1+ds/summary_test.go000066400000000000000000000105551414325366000247010ustar00rootroot00000000000000package metrics import ( "bytes" "fmt" "strings" "testing" "time" ) func TestSummarySerial(t *testing.T) { name := `TestSummarySerial` s := NewSummary(name) // Verify that the summary isn't visible in the output of WritePrometheus, // since it doesn't contain any values yet. var bb bytes.Buffer WritePrometheus(&bb, false) result := bb.String() if strings.Contains(result, name) { t.Fatalf("summary %s shouldn't be visible in the WritePrometheus output; got\n%s", name, result) } // Write data to summary for i := 0; i < 2000; i++ { s.Update(float64(i)) t := time.Now() s.UpdateDuration(t.Add(-time.Millisecond * time.Duration(i))) } // Make sure the summary prints _sum and _count on marshalTo call testMarshalTo(t, s, "prefix", fmt.Sprintf("prefix_sum %g\nprefix_count %d\n", s.sum, s.count)) testMarshalTo(t, s, `m{foo="bar"}`, fmt.Sprintf("m_sum{foo=\"bar\"} %g\nm_count{foo=\"bar\"} %d\n", s.sum, s.count)) // Verify s.quantileValues s.updateQuantiles() if s.quantileValues[len(s.quantileValues)-1] != 1999 { t.Fatalf("unexpected quantileValues[last]; got %v; want %v", s.quantileValues[len(s.quantileValues)-1], 1999) } // Make sure the summary becomes visible in the output of WritePrometheus, // since now it contains values. bb.Reset() WritePrometheus(&bb, false) result = bb.String() if !strings.Contains(result, name) { t.Fatalf("missing summary %s in the WritePrometheus output; got\n%s", name, result) } } func TestSummaryConcurrent(t *testing.T) { name := "SummaryConcurrent" s := NewSummary(name) err := testConcurrent(func() error { for i := 0; i < 10; i++ { s.Update(float64(i)) } return nil }) if err != nil { t.Fatal(err) } testMarshalTo(t, s, "prefix", "prefix_sum 225\nprefix_count 50\n") } func TestSummaryWithTags(t *testing.T) { name := `TestSummary{tag="foo"}` s := NewSummary(name) s.Update(123) var bb bytes.Buffer WritePrometheus(&bb, false) result := bb.String() namePrefixWithTag := `TestSummary{tag="foo",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() { NewSummaryExt(name, time.Minute, []float64{123, -234}) }) } func TestSummarySmallWindow(t *testing.T) { name := "SummarySmallWindow" window := time.Millisecond * 20 quantiles := []float64{0.1, 0.2, 0.3} s := NewSummaryExt(name, window, quantiles) for i := 0; i < 2000; i++ { s.Update(123) } // Wait for window update and verify that the summary has been cleared. time.Sleep(2 * window) var bb bytes.Buffer WritePrometheus(&bb, false) result := bb.String() // _sum and _count are present in the output. // Only {quantile} shouldn't be present. name += "{" if strings.Contains(result, name) { t.Fatalf("summary %s cannot be present in the WritePrometheus output; got\n%s", name, result) } } func TestGetOrCreateSummaryInvalidWindow(t *testing.T) { name := "GetOrCreateSummaryInvalidWindow" GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles) expectPanic(t, name, func() { GetOrCreateSummaryExt(name, defaultSummaryWindow/2, defaultSummaryQuantiles) }) } func TestGetOrCreateSummaryInvalidQuantiles(t *testing.T) { name := "GetOrCreateSummaryInvalidQuantiles" GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles) expectPanic(t, name, func() { GetOrCreateSummaryExt(name, defaultSummaryWindow, []float64{0.1, 0.2}) }) quantiles := append([]float64{}, defaultSummaryQuantiles...) quantiles[len(quantiles)-1] /= 2 expectPanic(t, name, func() { GetOrCreateSummaryExt(name, defaultSummaryWindow, quantiles) }) } func TestGetOrCreateSummarySerial(t *testing.T) { name := "GetOrCreateSummarySerial" if err := testGetOrCreateSummary(name); err != nil { t.Fatal(err) } } func TestGetOrCreateSummaryConcurrent(t *testing.T) { name := "GetOrCreateSummaryConcurrent" err := testConcurrent(func() error { return testGetOrCreateSummary(name) }) if err != nil { t.Fatal(err) } } func testGetOrCreateSummary(name string) error { s1 := GetOrCreateSummary(name) for i := 0; i < 10; i++ { s2 := GetOrCreateSummary(name) if s1 != s2 { return fmt.Errorf("unexpected summary returned; got %p; want %p", s2, s1) } } return nil } golang-github-victoriametrics-metrics-1.18.1+ds/testdata/000077500000000000000000000000001414325366000234215ustar00rootroot00000000000000golang-github-victoriametrics-metrics-1.18.1+ds/testdata/fd/000077500000000000000000000000001414325366000240125ustar00rootroot00000000000000golang-github-victoriametrics-metrics-1.18.1+ds/testdata/fd/0000066400000000000000000000000001414325366000240620ustar00rootroot00000000000000golang-github-victoriametrics-metrics-1.18.1+ds/testdata/fd/10000066400000000000000000000000001414325366000241430ustar00rootroot00000000000000golang-github-victoriametrics-metrics-1.18.1+ds/testdata/fd/2000066400000000000000000000000001414325366000240640ustar00rootroot00000000000000golang-github-victoriametrics-metrics-1.18.1+ds/testdata/fd/3000066400000000000000000000000001414325366000240650ustar00rootroot00000000000000golang-github-victoriametrics-metrics-1.18.1+ds/testdata/fd/5000066400000000000000000000000001414325366000240670ustar00rootroot00000000000000golang-github-victoriametrics-metrics-1.18.1+ds/testdata/limits000066400000000000000000000022741414325366000246520ustar00rootroot00000000000000Limit Soft Limit Hard Limit Units Max cpu time unlimited unlimited seconds Max file size unlimited unlimited bytes Max data size unlimited unlimited bytes Max stack size 8388608 unlimited bytes Max core file size 0 unlimited bytes Max resident set unlimited unlimited bytes Max processes 127458 127458 processes Max open files 1024 1048576 files Max locked memory 67108864 67108864 bytes Max address space unlimited unlimited bytes Max file locks unlimited unlimited locks Max pending signals 127458 127458 signals Max msgqueue size 819200 819200 bytes Max nice priority 0 0 Max realtime priority 0 0 Max realtime timeout unlimited unlimited usgolang-github-victoriametrics-metrics-1.18.1+ds/testdata/limits_bad000066400000000000000000000001111414325366000254440ustar00rootroot00000000000000Limit Soft Limit Hard Limit Unitsgolang-github-victoriametrics-metrics-1.18.1+ds/testdata/status000066400000000000000000000053301414325366000246700ustar00rootroot00000000000000Name: victoria-metric Umask: 0022 State: S (sleeping) Tgid: 1 Ngid: 0 Pid: 1 PPid: 0 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 256 Groups: 1 2 3 4 6 10 11 20 26 27 NStgid: 1 NSpid: 1 NSpgid: 1 NSsid: 1 VmPeak: 2080548 kB VmSize: 2080464 kB VmLck: 0 kB VmPin: 0 kB VmHWM: 195976 kB VmRSS: 105212 kB RssAnon: 94092 kB RssFile: 11120 kB RssShmem: 0 kB VmData: 632076 kB VmStk: 132 kB VmExe: 7004 kB VmLib: 8 kB VmPTE: 940 kB VmSwap: 0 kB HugetlbPages: 0 kB CoreDumping: 0 THP_enabled: 1 Threads: 14 SigQ: 1/127458 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: fffffffc3bfa3a00 SigIgn: 0000000000000000 SigCgt: fffffffdffc1feff CapInh: 00000000a80425fb CapPrm: 00000000a80425fb CapEff: 00000000a80425fb CapBnd: 00000000a80425fb CapAmb: 0000000000000000 NoNewPrivs: 0 Seccomp: 0 Speculation_Store_Bypass: thread vulnerable Cpus_allowed: ff Cpus_allowed_list: 0-7 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 82 nonvoluntary_ctxt_switches: 21 / # cat /proc/1/stat stat statm status / # cat /proc/1/statm 520122 27057 2780 1751 0 158052 0 / # cat /proc/1/status Name: victoria-metric Umask: 0022 State: S (sleeping) Tgid: 1 Ngid: 0 Pid: 1 PPid: 0 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 256 Groups: 1 2 3 4 6 10 11 20 26 27 NStgid: 1 NSpid: 1 NSpgid: 1 NSsid: 1 VmPeak: 2080556 kB VmSize: 2080520 kB VmLck: 0 kB VmPin: 0 kB VmHWM: 195976 kB VmRSS: 129848 kB RssAnon: 118752 kB RssFile: 11096 kB RssShmem: 0 kB VmData: 633020 kB VmStk: 132 kB VmExe: 7004 kB VmLib: 8 kB VmPTE: 984 kB VmSwap: 0 kB HugetlbPages: 0 kB CoreDumping: 0 THP_enabled: 1 Threads: 14 SigQ: 1/127458 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: fffffffc3bfa3a00 SigIgn: 0000000000000000 SigCgt: fffffffdffc1feff CapInh: 00000000a80425fb CapPrm: 00000000a80425fb CapEff: 00000000a80425fb CapBnd: 00000000a80425fb CapAmb: 0000000000000000 NoNewPrivs: 0 Seccomp: 0 Speculation_Store_Bypass: thread vulnerable Cpus_allowed: ff Cpus_allowed_list: 0-7 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 82 nonvoluntary_ctxt_switches: 21golang-github-victoriametrics-metrics-1.18.1+ds/testdata/status_bad000066400000000000000000000053371414325366000255050ustar00rootroot00000000000000Name: victoria-metric Umask: 0022 State: S (sleeping) Tgid: 1 Ngid: 0 Pid: 1 PPid: 0 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 256 Groups: 1 2 3 4 6 10 11 20 26 27 NStgid: 1 NSpid: 1 NSpgid: 1 NSsid: 1 VmPeak: 2080548 kB VmSize: 2080464 kB VmLck: 0 kB VmPin: 0 kB VmHWM: 195976 kB VmRSS: 105212 kB RssAnon: 94092 kB RssFile: 11120 kB RssShmem: 0 kB VmData: 632076 kB VmStk: 132 kB VmExe: 7004 kB VmLib: 8 kB VmPTE: 940 kB VmSwap: 0 kB HugetlbPages: 0 kB CoreDumping: 0 THP_enabled: 1 Threads: 14 SigQ: 1/127458 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: fffffffc3bfa3a00 SigIgn: 0000000000000000 SigCgt: fffffffdffc1feff CapInh: 00000000a80425fb CapPrm: 00000000a80425fb CapEff: 00000000a80425fb CapBnd: 00000000a80425fb CapAmb: 0000000000000000 NoNewPrivs: 0 Seccomp: 0 Speculation_Store_Bypass: thread vulnerable Cpus_allowed: ff Cpus_allowed_list: 0-7 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 82 nonvoluntary_ctxt_switches: 21 / # cat /proc/1/stat stat statm status / # cat /proc/1/statm 520122 27057 2780 1751 0 158052 0 / # cat /proc/1/status Name: victoria-metric Umask: 0022 State: S (sleeping) Tgid: 1 Ngid: 0 Pid: 1 PPid: 0 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 256 Groups: 1 2 3 4 6 10 11 20 26 27 NStgid: 1 NSpid: 1 NSpgid: 1 NSsid: 1 VmPeak: 2080556 kB VmSize: 2080520 kB as VmLck: 0 kB VmPin: 0 kB VmHWM: 195976 kB VmRSS: 129848 kB RssAnon: 118752 kB RssFile: 11096 kB RssShmem: 0 kB VmData: 633020 kB VmStk: 132 kB VmExe: 7004 kB VmLib: 8 kB VmPTE: 984 kB VmSwap: 0 kB HugetlbPages: 0 kB CoreDumping: 0 THP_enabled: 1 Threads: 14 SigQ: 1/127458 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: fffffffc3bfa3a00 fsa SigIgn: 0000000000000000 SigCgt: fffffffdffc1feff CapInh: 00000000a80425fb CapPrm: 00000000a80425fb CapEff: 00000000a80425fb CapBnd: 00000000a80425fb CapAmb: 0000000000000000 NoNewPrivs: 0 Seccomp: 0 Speculation_Store_Bypass: thread vulnerable Cpus_allowed: ff Cpus_allowed_list: 0-7 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 82 nonvoluntary_ctxt_switches: 21golang-github-victoriametrics-metrics-1.18.1+ds/validator.go000066400000000000000000000031121414325366000241210ustar00rootroot00000000000000package metrics import ( "fmt" "regexp" "strings" ) func validateMetric(s string) error { if len(s) == 0 { return fmt.Errorf("metric cannot be empty") } n := strings.IndexByte(s, '{') if n < 0 { return validateIdent(s) } ident := s[:n] s = s[n+1:] if err := validateIdent(ident); err != nil { return err } if len(s) == 0 || s[len(s)-1] != '}' { return fmt.Errorf("missing closing curly brace at the end of %q", ident) } return validateTags(s[:len(s)-1]) } func validateTags(s string) error { if len(s) == 0 { return nil } for { n := strings.IndexByte(s, '=') if n < 0 { return fmt.Errorf("missing `=` after %q", s) } ident := s[:n] s = s[n+1:] if err := validateIdent(ident); err != nil { return err } if len(s) == 0 || s[0] != '"' { return fmt.Errorf("missing starting `\"` for %q value; tail=%q", ident, s) } s = s[1:] again: n = strings.IndexByte(s, '"') if n < 0 { return fmt.Errorf("missing trailing `\"` for %q value; tail=%q", ident, s) } m := n for m > 0 && s[m-1] == '\\' { m-- } if (n-m)%2 == 1 { s = s[n+1:] goto again } s = s[n+1:] if len(s) == 0 { return nil } if !strings.HasPrefix(s, ",") { return fmt.Errorf("missing `,` after %q value; tail=%q", ident, s) } s = skipSpace(s[1:]) } } func skipSpace(s string) string { for len(s) > 0 && s[0] == ' ' { s = s[1:] } return s } func validateIdent(s string) error { if !identRegexp.MatchString(s) { return fmt.Errorf("invalid identifier %q", s) } return nil } var identRegexp = regexp.MustCompile("^[a-zA-Z_:.][a-zA-Z0-9_:.]*$") golang-github-victoriametrics-metrics-1.18.1+ds/validator_test.go000066400000000000000000000017631414325366000251720ustar00rootroot00000000000000package metrics import ( "testing" ) func TestValidateMetricSuccess(t *testing.T) { f := func(s string) { t.Helper() if err := validateMetric(s); err != nil { t.Fatalf("cannot validate %q: %s", s, err) } } f("a") f("_9:8") f("a{}") f(`a{foo="bar"}`) f(`foo{bar="baz", x="y\"z"}`) f(`foo{bar="b}az"}`) f(`:foo:bar{bar="a",baz="b"}`) f(`some.foo{bar="baz"}`) } func TestValidateMetricError(t *testing.T) { f := func(s string) { t.Helper() if err := validateMetric(s); err == nil { t.Fatalf("expecting non-nil error when validating %q", s) } } f("") f("{}") // superflouos space f("a ") f(" a") f(" a ") f("a {}") f("a{} ") f("a{ }") f(`a{foo ="bar"}`) f(`a{ foo="bar"}`) f(`a{foo= "bar"}`) f(`a{foo="bar" }`) f(`a{foo="bar" ,baz="a"}`) // invalid tags f("a{foo}") f("a{=}") f(`a{=""}`) f(`a{`) f(`a}`) f(`a{foo=}`) f(`a{foo="`) f(`a{foo="}`) f(`a{foo="bar",}`) f(`a{foo="bar", x`) f(`a{foo="bar", x=`) f(`a{foo="bar", x="`) f(`a{foo="bar", x="}`) }