From cf41206fecfe9e37878e542a0f18a155d5e70e3f Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Fri, 26 May 2023 17:48:40 +0300 Subject: [PATCH] [#1] metrics: Add metrics pkg Signed-off-by: Dmitrii Stepanov --- metrics/desc.go | 112 +++++++++++++++++++++++++++++++++++++++++++ metrics/desc_test.go | 64 +++++++++++++++++++++++++ metrics/go.mod | 25 ++++++++++ metrics/go.sum | 45 +++++++++++++++++ metrics/registry.go | 27 +++++++++++ 5 files changed, 273 insertions(+) create mode 100644 metrics/desc.go create mode 100644 metrics/desc_test.go create mode 100644 metrics/go.mod create mode 100644 metrics/go.sum create mode 100644 metrics/registry.go diff --git a/metrics/desc.go b/metrics/desc.go new file mode 100644 index 0000000..7d8f305 --- /dev/null +++ b/metrics/desc.go @@ -0,0 +1,112 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" +) + +// Descriptions contains metric Description suitable for further processing. +// The only reason for it to exist is `prometheus.Desc` disallowing field access directly. +// https://github.com/prometheus/client_golang/pull/326 +// https://github.com/prometheus/client_golang/issues/516 +// https://github.com/prometheus/client_golang/issues/222 +type Description struct { + Name string `json:"name"` + Help string `json:"help"` + Type string `json:"type"` + ConstantLabels prometheus.Labels `json:"constant_labels,omitempty"` + VariableLabels []string `json:"variable_labels,omitempty"` +} + +// NewGauge returns new registered prometheus.Gauge. +func NewGauge(opts prometheus.GaugeOpts) prometheus.Gauge { + value := prometheus.NewGauge(opts) + mustRegister(value, Description{ + Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), + Type: dto.MetricType_GAUGE.String(), + Help: opts.Help, + ConstantLabels: opts.ConstLabels, + }) + return value +} + +// NewGaugeVec returns new registered *prometheus.GaugeVec. +func NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *prometheus.GaugeVec { + value := prometheus.NewGaugeVec(opts, labelNames) + mustRegister(value, Description{ + Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), + Type: dto.MetricType_GAUGE.String(), + Help: opts.Help, + ConstantLabels: opts.ConstLabels, + VariableLabels: labelNames, + }) + + return value +} + +// NewGaugeFunc returns new registered prometheus.GaugeFunc. +func NewGaugeFunc(opts prometheus.GaugeOpts, f func() float64) prometheus.GaugeFunc { + value := prometheus.NewGaugeFunc(opts, f) + mustRegister(value, Description{ + Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), + Type: dto.MetricType_GAUGE.String(), + Help: opts.Help, + ConstantLabels: opts.ConstLabels, + }) + return value +} + +// NewCounter returns new registered prometheus.Counter. +func NewCounter(opts prometheus.CounterOpts) prometheus.Counter { + value := prometheus.NewCounter(opts) + mustRegister(value, Description{ + Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), + Type: dto.MetricType_COUNTER.String(), + Help: opts.Help, + ConstantLabels: opts.ConstLabels, + }) + return value +} + +// NewCounterVec returns new registered *prometheus.CounterVec. +func NewCounterVec(opts prometheus.CounterOpts, labels []string) *prometheus.CounterVec { + value := prometheus.NewCounterVec(opts, labels) + mustRegister(value, Description{ + Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), + Type: dto.MetricType_COUNTER.String(), + Help: opts.Help, + ConstantLabels: opts.ConstLabels, + VariableLabels: labels, + }) + return value +} + +// NewHistogramVec returns new registered *prometheus.HistogramVec. +func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *prometheus.HistogramVec { + value := prometheus.NewHistogramVec(opts, labelNames) + mustRegister(value, Description{ + Name: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), + Type: dto.MetricType_HISTOGRAM.String(), + Help: opts.Help, + ConstantLabels: opts.ConstLabels, + VariableLabels: labelNames, + }) + return value +} + +// DescribeAll returns descriptions for all registered metrics. +func DescribeAll() []Description { + registeredDescriptionsMtx.Lock() + defer registeredDescriptionsMtx.Unlock() + + ds := make([]Description, len(registeredDescriptions)) + copy(ds, registeredDescriptions) + return ds +} + +func mustRegister(c prometheus.Collector, desc Description) { + registry.MustRegister(c) + registeredDescriptionsMtx.Lock() + defer registeredDescriptionsMtx.Unlock() + registeredDescriptions = append(registeredDescriptions, desc) +} diff --git a/metrics/desc_test.go b/metrics/desc_test.go new file mode 100644 index 0000000..fc0fb28 --- /dev/null +++ b/metrics/desc_test.go @@ -0,0 +1,64 @@ +package metrics + +import ( + "strings" + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" +) + +func TestDescribeAll(t *testing.T) { + const ( + namespace = "my_ns" + subsystem = "mysub" + ) + NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "my_counter", + }) + + labels := []string{"label1", "label2"} + NewGaugeVec(prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "my_gauge", + }, labels) + + constLabels := prometheus.Labels{ + "const1": "abc", + "const2": "xyz", + } + NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "with_const_labels", + ConstLabels: constLabels, + }) + + descriptions := DescribeAll() + + seen := make(map[string]bool) + for i := range descriptions { + if !strings.HasPrefix(descriptions[i].Name, namespace) { + continue + } + + require.False(t, seen[descriptions[i].Name], "metric %s was seen twice", descriptions[i].Name) + seen[descriptions[i].Name] = true + + switch descriptions[i].Name { + case prometheus.BuildFQName(namespace, subsystem, "my_counter"): + require.True(t, len(descriptions[i].VariableLabels) == 0) + case prometheus.BuildFQName(namespace, subsystem, "my_gauge"): + require.Equal(t, labels, descriptions[i].VariableLabels) + case prometheus.BuildFQName(namespace, subsystem, "with_const_labels"): + require.Equal(t, len(constLabels), len(descriptions[i].ConstantLabels)) + require.Equal(t, constLabels, descriptions[i].ConstantLabels) + default: + require.FailNow(t, "unexpected metric name: %s", descriptions[i].Name) + } + } + require.Equal(t, 3, len(seen), "not all registered metrics were iterated over") +} diff --git a/metrics/go.mod b/metrics/go.mod new file mode 100644 index 0000000..60e4c88 --- /dev/null +++ b/metrics/go.mod @@ -0,0 +1,25 @@ +module git.frostfs.info/TrueCloudLab/frostfs-platform/metrics + +go 1.19 + +require ( + github.com/prometheus/client_golang v1.15.1 + github.com/prometheus/client_model v0.3.0 + github.com/stretchr/testify v1.8.3 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + golang.org/x/sys v0.6.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/metrics/go.sum b/metrics/go.sum new file mode 100644 index 0000000..bafdd33 --- /dev/null +++ b/metrics/go.sum @@ -0,0 +1,45 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/metrics/registry.go b/metrics/registry.go new file mode 100644 index 0000000..52d3cf6 --- /dev/null +++ b/metrics/registry.go @@ -0,0 +1,27 @@ +package metrics + +import ( + "sync" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" +) + +var ( + registry = prometheus.NewRegistry() + // registeredDescriptionsMtx protects collectors slice. + // It should not be acessed concurrently, but we can easily forget this in future, thus this mutex. + registeredDescriptionsMtx sync.Mutex + registeredDescriptions []Description +) + +func init() { + registry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})) + registry.MustRegister(collectors.NewGoCollector()) +} + +// Register registers custom collectors to registry. +// Should be used with metrics from other packages. +func Register(customCollectors ...prometheus.Collector) { + registry.MustRegister(customCollectors...) +}