From 997f062023a6088c5e60cced7d4e0aad612ab220 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Tue, 4 Mar 2025 14:31:15 +0300 Subject: [PATCH] fixup and copy from v4 Signed-off-by: Vasiliy Tolstov --- .gitea/ISSUE_TEMPLATE/bug_report.md | 8 +- .../feature-request---enhancement.md | 4 +- .gitea/ISSUE_TEMPLATE/question.md | 10 +- counter.go | 93 --- gauge.go | 13 - go.mod | 22 +- go.sum | 30 +- histogram.go | 39 -- prometheus.go | 588 +++++++++++------- prometheus_test.go | 168 +---- summary.go | 26 - 11 files changed, 420 insertions(+), 581 deletions(-) delete mode 100644 counter.go delete mode 100644 gauge.go delete mode 100644 histogram.go delete mode 100644 summary.go diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.md b/.gitea/ISSUE_TEMPLATE/bug_report.md index 1899438..a77fdfd 100644 --- a/.gitea/ISSUE_TEMPLATE/bug_report.md +++ b/.gitea/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: For reporting bugs in go-micro +about: For reporting bugs in micro title: "[BUG]" labels: '' assignees: '' @@ -16,9 +16,3 @@ assignees: '' **How to reproduce the bug:** If possible, please include a minimal code snippet here. - -**Environment:** -Go Version: please paste `go version` output here -``` -please paste `go env` output here -``` diff --git a/.gitea/ISSUE_TEMPLATE/feature-request---enhancement.md b/.gitea/ISSUE_TEMPLATE/feature-request---enhancement.md index 459817f..c722ea6 100644 --- a/.gitea/ISSUE_TEMPLATE/feature-request---enhancement.md +++ b/.gitea/ISSUE_TEMPLATE/feature-request---enhancement.md @@ -1,6 +1,6 @@ --- name: Feature request / Enhancement -about: If you have a need not served by go-micro +about: If you have a need not served by micro title: "[FEATURE]" labels: '' assignees: '' @@ -14,4 +14,4 @@ A clear and concise description of what the problem is. Ex. I'm always frustrate A clear and concise description of what you want to happen. **Additional context** -Add any other context or screenshots about the feature request here. +Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.gitea/ISSUE_TEMPLATE/question.md b/.gitea/ISSUE_TEMPLATE/question.md index 1daf48b..3d65c72 100644 --- a/.gitea/ISSUE_TEMPLATE/question.md +++ b/.gitea/ISSUE_TEMPLATE/question.md @@ -1,14 +1,8 @@ --- name: Question -about: Ask a question about go-micro +about: Ask a question about micro title: '' labels: '' assignees: '' ---- - -Before asking, please check if your question has already been answered: - -1. Check the documentation - https://micro.mu/docs/ -2. Check the examples and plugins - https://github.com/micro/examples & https://github.com/micro/go-plugins -3. Search existing issues +--- \ No newline at end of file diff --git a/counter.go b/counter.go deleted file mode 100644 index dee3ca7..0000000 --- a/counter.go +++ /dev/null @@ -1,93 +0,0 @@ -package prometheus - -import ( - "math" - "sync/atomic" - "unsafe" - - dto "github.com/prometheus/client_model/go" -) - -type prometheusCounter struct { - name string - c *dto.Metric - n float64 -} - -func (c *prometheusCounter) Add(n int) { - addFloat64(&(c.n), float64(n)) -} - -func (c *prometheusCounter) Dec() { - addFloat64(&(c.n), float64(-1)) -} - -func (c *prometheusCounter) Inc() { - addFloat64(&(c.n), float64(1)) -} - -func (c *prometheusCounter) Get() uint64 { - return uint64(getFloat64(&(c.n))) -} - -func (c *prometheusCounter) Set(n uint64) { - setFloat64(&(c.n), math.Float64frombits(n)) -} - -type prometheusFloatCounter struct { - name string - c *dto.Metric - n float64 -} - -func (c *prometheusFloatCounter) Add(n float64) { - addFloat64(&(c.n), n) -} - -func (c *prometheusFloatCounter) Dec() { - addFloat64(&(c.n), float64(-1)) -} - -func (c *prometheusFloatCounter) Inc() { - addFloat64(&(c.n), float64(1)) -} - -func (c *prometheusFloatCounter) Get() float64 { - return getFloat64(&(c.n)) -} - -func (c *prometheusFloatCounter) Set(n float64) { - setFloat64(&(c.n), n) -} - -func (c *prometheusFloatCounter) Sub(n float64) { - addFloat64(&(c.n), -n) -} - -func setFloat64(_addr *float64, value float64) float64 { - addr := (*uint64)(unsafe.Pointer(_addr)) - for { - x := atomic.LoadUint64(addr) - if atomic.CompareAndSwapUint64(addr, x, math.Float64bits(value)) { - return value - } - } -} - -func addFloat64(_addr *float64, delta float64) float64 { - addr := (*uint64)(unsafe.Pointer(_addr)) - for { - x := atomic.LoadUint64(addr) - y := math.Float64frombits(x) + delta - if atomic.CompareAndSwapUint64(addr, x, math.Float64bits(y)) { - return y - } - } -} - -func getFloat64(_addr *float64) float64 { - addr := (*uint64)(unsafe.Pointer(_addr)) - x := atomic.LoadUint64(addr) - y := math.Float64frombits(x) - return y -} diff --git a/gauge.go b/gauge.go deleted file mode 100644 index 7126cdf..0000000 --- a/gauge.go +++ /dev/null @@ -1,13 +0,0 @@ -package prometheus - -import dto "github.com/prometheus/client_model/go" - -type prometheusGauge struct { - name string - c *dto.Metric - n float64 -} - -func (c *prometheusGauge) Get() float64 { - return getFloat64(&(c.n)) -} diff --git a/go.mod b/go.mod index 5215463..090ea7f 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,21 @@ -module go.unistack.org/micro-meter-prometheus/v3 +module go.unistack.org/micro-meter-prometheus/v4 go 1.22.0 +toolchain go1.24.0 + require ( - github.com/prometheus/client_golang v1.20.4 + github.com/prometheus/client_golang v1.21.0 github.com/prometheus/client_model v0.6.1 - github.com/prometheus/common v0.59.1 - go.unistack.org/micro/v3 v3.10.91 - google.golang.org/protobuf v1.35.2 + github.com/prometheus/common v0.62.0 + go.unistack.org/micro/v3 v3.11.41 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/klauspost/compress v1.17.11 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/stretchr/testify v1.10.0 // indirect - go.unistack.org/micro-proto/v3 v3.4.1 // indirect - golang.org/x/net v0.32.0 // indirect - golang.org/x/sys v0.28.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/grpc v1.68.1 // indirect + golang.org/x/sys v0.30.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect ) diff --git a/go.sum b/go.sum index 44098af..d732b9a 100644 --- a/go.sum +++ b/go.sum @@ -3,32 +3,30 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= +github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= -github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -go.unistack.org/micro-proto/v3 v3.4.1 h1:UTjLSRz2YZuaHk9iSlVqqsA50JQNAEK2ZFboGqtEa9Q= -go.unistack.org/micro-proto/v3 v3.4.1/go.mod h1:okx/cnOhzuCX0ggl/vToatbCupi0O44diiiLLsZ93Zo= -go.unistack.org/micro/v3 v3.10.91 h1:vuJY4tXwpqimwIkEJ3TozMYNVQQs+C5QMlQWPgSY/YM= -go.unistack.org/micro/v3 v3.10.91/go.mod h1:erMgt3Bl7vQQ0e9UpQyR5NlLiZ9pKeEJ9+1tfYFaqUg= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= -google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.unistack.org/micro/v3 v3.11.41 h1:dP4sBLIZpMo+MWGe5bbESewK8wBzYm4Yik/67x4dEtQ= +go.unistack.org/micro/v3 v3.11.41/go.mod h1:POGU5hstnAT9LH70m8FalyQSNi2GfIew71K75JenIZk= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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/histogram.go b/histogram.go deleted file mode 100644 index 61e746b..0000000 --- a/histogram.go +++ /dev/null @@ -1,39 +0,0 @@ -package prometheus - -import ( - "sync/atomic" - "time" - - dto "github.com/prometheus/client_model/go" -) - -type prometheusHistogram struct { - name string - c *dto.Metric -} - -func (c prometheusHistogram) Reset() { -} - -func (c prometheusHistogram) Update(n float64) { - atomic.AddUint64(c.c.Histogram.SampleCount, 1) - addFloat64(c.c.Histogram.SampleSum, n) - for _, b := range c.c.Histogram.Bucket { - if n > *b.UpperBound { - continue - } - atomic.AddUint64(b.CumulativeCount, 1) - } -} - -func (c prometheusHistogram) UpdateDuration(n time.Time) { - x := time.Since(n).Seconds() - atomic.AddUint64(c.c.Histogram.SampleCount, 1) - addFloat64(c.c.Histogram.SampleSum, x) - for _, b := range c.c.Histogram.Bucket { - if x > *b.UpperBound { - continue - } - atomic.AddUint64(b.CumulativeCount, 1) - } -} diff --git a/prometheus.go b/prometheus.go index 2bbe1cd..b4488f3 100644 --- a/prometheus.go +++ b/prometheus.go @@ -2,10 +2,10 @@ package prometheus import ( "fmt" + "hash/fnv" "io" "regexp" "sync" - "sync/atomic" "time" "github.com/prometheus/client_golang/prometheus" @@ -13,205 +13,232 @@ import ( dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "go.unistack.org/micro/v3/meter" - xpool "go.unistack.org/micro/v3/util/xpool" - "google.golang.org/protobuf/types/known/timestamppb" ) -var _ meter.Meter = (*prometheusMeter)(nil) +var _ meter.Meter = &prometheusMeter{} type prometheusMeter struct { opts meter.Options set prometheus.Registerer - counter sync.Map - floatCounter sync.Map - gauge sync.Map - histogram sync.Map - summary sync.Map - mfPool xpool.Pool[*dto.MetricFamily] - mu sync.Mutex + counter map[string]*counters + floatCounter map[string]*floatCounters + gauge map[string]*gauges + histogram map[string]*histograms + summary map[string]*summaries + sync.Mutex +} + +type counters struct { + cs map[uint64]*prometheusCounter +} + +type gauges struct { + cs map[uint64]*prometheusGauge +} + +type histograms struct { + cs map[uint64]*prometheusHistogram +} + +type summaries struct { + cs map[uint64]*prometheusSummary +} + +type floatCounters struct { + cs map[uint64]*prometheusFloatCounter +} + +/* +func newFloat64(v float64) *float64 { + nv := v + return &nv +} +*/ + +func newString(v string) *string { + nv := v + return &nv } func NewMeter(opts ...meter.Option) *prometheusMeter { return &prometheusMeter{ - set: prometheus.NewRegistry(), // prometheus.DefaultRegisterer, - opts: meter.NewOptions(opts...), - mfPool: xpool.NewPool[*dto.MetricFamily](func() *dto.MetricFamily { - return &dto.MetricFamily{} - }), + set: prometheus.NewRegistry(), // prometheus.DefaultRegisterer, + opts: meter.NewOptions(opts...), + counter: make(map[string]*counters), + floatCounter: make(map[string]*floatCounters), + gauge: make(map[string]*gauges), + histogram: make(map[string]*histograms), + summary: make(map[string]*summaries), } } +func (m *prometheusMeter) buildMetric(name string, labels ...string) string { + nl := len(m.opts.Labels) + len(labels) + if nl == 0 { + return name + } + + nlabels := make([]string, 0, nl) + nlabels = append(nlabels, m.opts.Labels...) + nlabels = append(nlabels, labels...) + + return meter.BuildName(name, nlabels...) +} + func (m *prometheusMeter) Name() string { return m.opts.Name } func (m *prometheusMeter) Counter(name string, labels ...string) meter.Counter { - clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) - h := newHash(name, clabels) - m.mu.Lock() - c, ok := m.counter.Load(h) - // fmt.Printf("counter name %s hash %v labels %v\n", name, h, labels) - m.mu.Unlock() + m.Lock() + defer m.Unlock() + nm := m.buildMetric(name) + labels = append(m.opts.Labels, labels...) + cd, ok := m.counter[nm] + h := newHash(labels) if !ok { - var n float64 - c = &prometheusCounter{ - name: name, - c: &dto.Metric{ - Gauge: &dto.Gauge{Value: &n}, - Label: labelMetric(clabels), - }, - } - m.mu.Lock() - m.counter.Store(h, c) - m.mu.Unlock() + cd = &counters{cs: make(map[uint64]*prometheusCounter)} + c := &prometheusCounter{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: nm}), labels: labels} + cd.cs[h] = c + m.counter[nm] = cd + return c } - return c.(*prometheusCounter) + c, ok := cd.cs[h] + if !ok { + c = &prometheusCounter{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: nm}), labels: labels} + cd.cs[h] = c + m.counter[nm] = cd + } + return c } func (m *prometheusMeter) FloatCounter(name string, labels ...string) meter.FloatCounter { - clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) - h := newHash(name, clabels) - m.mu.Lock() - c, ok := m.counter.Load(h) - m.mu.Unlock() + m.Lock() + defer m.Unlock() + nm := m.buildMetric(name) + labels = append(m.opts.Labels, labels...) + cd, ok := m.floatCounter[nm] + h := newHash(labels) if !ok { - var n float64 - c = &prometheusFloatCounter{ - name: name, - c: &dto.Metric{ - Gauge: &dto.Gauge{Value: &n}, - Label: labelMetric(clabels), - }, - } - m.mu.Lock() - m.counter.Store(h, c) - m.mu.Unlock() + cd = &floatCounters{cs: make(map[uint64]*prometheusFloatCounter)} + c := &prometheusFloatCounter{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: nm}), labels: labels} + cd.cs[h] = c + m.floatCounter[nm] = cd + return c } - return c.(*prometheusFloatCounter) + c, ok := cd.cs[h] + if !ok { + c = &prometheusFloatCounter{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: nm}), labels: labels} + cd.cs[h] = c + m.floatCounter[nm] = cd + } + return c } func (m *prometheusMeter) Gauge(name string, fn func() float64, labels ...string) meter.Gauge { - clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) - h := newHash(name, clabels) - m.mu.Lock() - c, ok := m.gauge.Load(h) - m.mu.Unlock() + m.Lock() + defer m.Unlock() + nm := m.buildMetric(name) + labels = append(m.opts.Labels, labels...) + cd, ok := m.gauge[nm] + h := newHash(labels) if !ok { - var n float64 - c = &prometheusGauge{ - name: name, - c: &dto.Metric{ - Gauge: &dto.Gauge{Value: &n}, - Label: labelMetric(clabels), - }, - } - m.mu.Lock() - m.gauge.Store(h, c) - m.mu.Unlock() + cd = &gauges{cs: make(map[uint64]*prometheusGauge)} + c := &prometheusGauge{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: nm}), labels: labels} + cd.cs[h] = c + m.gauge[nm] = cd + return c } - return c.(*prometheusGauge) + c, ok := cd.cs[h] + if !ok { + c = &prometheusGauge{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: nm}), labels: labels} + cd.cs[h] = c + m.gauge[nm] = cd + } + return c } func (m *prometheusMeter) Histogram(name string, labels ...string) meter.Histogram { - clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) - h := newHash(name, clabels) - m.mu.Lock() - c, ok := m.histogram.Load(h) - m.mu.Unlock() + m.Lock() + defer m.Unlock() + nm := m.buildMetric(name) + labels = append(m.opts.Labels, labels...) + cd, ok := m.histogram[nm] + h := newHash(labels) if !ok { - var n uint64 - var s float64 - buckets := make([]float64, len(prometheus.DefBuckets)) - copy(buckets, prometheus.DefBuckets) - mdto := &dto.Metric{ - Histogram: &dto.Histogram{ - SampleCount: &n, - SampleSum: &s, - CreatedTimestamp: timestamppb.Now(), - Bucket: make([]*dto.Bucket, len(buckets)), - }, - Label: labelMetric(clabels), - } - for idx, b := range buckets { - var cc uint64 - mdto.Histogram.Bucket[idx] = &dto.Bucket{CumulativeCount: &cc, UpperBound: &b} - } - c = &prometheusHistogram{ - name: name, - c: mdto, - } - m.mu.Lock() - m.histogram.Store(h, c) - m.mu.Unlock() + cd = &histograms{cs: make(map[uint64]*prometheusHistogram)} + c := &prometheusHistogram{c: prometheus.NewHistogram(prometheus.HistogramOpts{Name: nm}), labels: labels} + cd.cs[h] = c + m.histogram[nm] = cd + return c } - return c.(*prometheusHistogram) + c, ok := cd.cs[h] + if !ok { + c = &prometheusHistogram{c: prometheus.NewHistogram(prometheus.HistogramOpts{Name: nm}), labels: labels} + cd.cs[h] = c + m.histogram[nm] = cd + } + return c } func (m *prometheusMeter) Summary(name string, labels ...string) meter.Summary { - clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) - h := newHash(name, clabels) - m.mu.Lock() - c, ok := m.summary.Load(h) - m.mu.Unlock() + m.Lock() + defer m.Unlock() + nm := m.buildMetric(name) + labels = append(m.opts.Labels, labels...) + cd, ok := m.summary[nm] + h := newHash(labels) if !ok { - var n uint64 - var s float64 - c = &prometheusSummary{ - name: name, - c: &dto.Metric{ - Summary: &dto.Summary{ - SampleCount: &n, - SampleSum: &s, - CreatedTimestamp: timestamppb.Now(), - }, - Label: labelMetric(clabels), - }, - } - m.mu.Lock() - m.summary.Store(h, c) - m.mu.Unlock() + cd = &summaries{cs: make(map[uint64]*prometheusSummary)} + c := &prometheusSummary{c: prometheus.NewSummary(prometheus.SummaryOpts{Name: nm}), labels: labels} + cd.cs[h] = c + m.summary[nm] = cd + return c } - return c.(*prometheusSummary) + c, ok := cd.cs[h] + if !ok { + c = &prometheusSummary{c: prometheus.NewSummary(prometheus.SummaryOpts{Name: nm}), labels: labels} + cd.cs[h] = c + m.summary[nm] = cd + } + return c } func (m *prometheusMeter) SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) meter.Summary { - clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) - h := newHash(name, clabels) - m.mu.Lock() - c, ok := m.summary.Load(h) - m.mu.Lock() + m.Lock() + defer m.Unlock() + nm := m.buildMetric(name) + labels = append(m.opts.Labels, labels...) + cd, ok := m.summary[nm] + h := newHash(labels) if !ok { - var n uint64 - var s float64 - c = &prometheusSummary{ - name: name, - c: &dto.Metric{ - Summary: &dto.Summary{ - SampleCount: &n, - SampleSum: &s, - }, - Label: labelMetric(clabels), - }, - } - m.mu.Lock() - m.summary.Store(h, c) - m.mu.Unlock() + cd = &summaries{cs: make(map[uint64]*prometheusSummary)} + c := &prometheusSummary{c: prometheus.NewSummary(prometheus.SummaryOpts{ + Name: nm, + MaxAge: window, + Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, + }), labels: labels} + cd.cs[h] = c + m.summary[nm] = cd + return c } - return c.(*prometheusSummary) + c, ok := cd.cs[h] + if !ok { + c = &prometheusSummary{c: prometheus.NewSummary(prometheus.SummaryOpts{ + Name: nm, + MaxAge: window, + Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, + }), labels: labels} + cd.cs[h] = c + m.summary[nm] = cd + } + return c } func (m *prometheusMeter) Init(opts ...meter.Option) error { for _, o := range opts { o(&m.opts) } - - if m.opts.WriteProcessMetrics || m.opts.WriteFDMetrics { - pc := collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}) - _ = m.set.Register(pc) - gc := collectors.NewGoCollector(collectors.WithGoCollectorRuntimeMetrics(collectors.GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")})) - _ = m.set.Register(gc) - } - return nil } @@ -221,6 +248,13 @@ func (m *prometheusMeter) Write(w io.Writer, opts ...meter.Option) error { o(&options) } + if options.WriteProcessMetrics || options.WriteFDMetrics { + pc := collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}) + _ = m.set.Register(pc) + gc := collectors.NewGoCollector(collectors.WithGoCollectorRuntimeMetrics(collectors.GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")})) + _ = m.set.Register(gc) + } + g, ok := m.set.(prometheus.Gatherer) if !ok { return fmt.Errorf("set type %T not prometheus.Gatherer", m.set) @@ -233,74 +267,83 @@ func (m *prometheusMeter) Write(w io.Writer, opts ...meter.Option) error { enc := expfmt.NewEncoder(w, expfmt.NewFormat(expfmt.TypeTextPlain)) - m.mu.Lock() - - m.counter.Range(func(_ any, v any) bool { - mc := v.(*prometheusCounter) - mf := m.mfPool.Get() - mf.Name = &mc.name - mf.Type = dto.MetricType_GAUGE.Enum() - n := getFloat64(&(mc.n)) - mc.c.Gauge.Value = &n - mf.Metric = append(mf.Metric, mc.c) + for name, metrics := range m.counter { + mf := &dto.MetricFamily{ + Name: newString(name), + Type: dto.MetricType_GAUGE.Enum(), + Metric: make([]*dto.Metric, 0, len(metrics.cs)), + } + for _, c := range metrics.cs { + m := &dto.Metric{} + _ = c.c.Write(m) + fillMetric(m, c.labels) + mf.Metric = append(mf.Metric, m) + } mfs = append(mfs, mf) - return true - }) + } - m.floatCounter.Range(func(_ any, v any) bool { - mc := v.(*prometheusFloatCounter) - mf := m.mfPool.Get() - mf.Name = &mc.name - mf.Type = dto.MetricType_GAUGE.Enum() - n := getFloat64(&(mc.n)) - mc.c.Gauge.Value = &n - mf.Metric = append(mf.Metric, mc.c) + for name, metrics := range m.gauge { + mf := &dto.MetricFamily{ + Name: newString(name), + Type: dto.MetricType_GAUGE.Enum(), + Metric: make([]*dto.Metric, 0, len(metrics.cs)), + } + for _, c := range metrics.cs { + m := &dto.Metric{} + _ = c.c.Write(m) + fillMetric(m, c.labels) + mf.Metric = append(mf.Metric, m) + } mfs = append(mfs, mf) - return true - }) + } - m.gauge.Range(func(_ any, v any) bool { - mc := v.(*prometheusGauge) - mf := m.mfPool.Get() - mf.Name = &mc.name - mf.Type = dto.MetricType_GAUGE.Enum() - n := getFloat64(&(mc.n)) - mc.c.Gauge.Value = &n - mf.Metric = append(mf.Metric, mc.c) + for name, metrics := range m.floatCounter { + mf := &dto.MetricFamily{ + Name: newString(name), + Type: dto.MetricType_GAUGE.Enum(), + Metric: make([]*dto.Metric, 0, len(metrics.cs)), + } + for _, c := range metrics.cs { + m := &dto.Metric{} + _ = c.c.Write(m) + fillMetric(m, c.labels) + mf.Metric = append(mf.Metric, m) + } mfs = append(mfs, mf) - return true - }) + } - m.histogram.Range(func(_ any, v any) bool { - c := v.(*prometheusHistogram) - mf := m.mfPool.Get() - mf.Name = &c.name - mf.Type = dto.MetricType_HISTOGRAM.Enum() - mf.Metric = append(mf.Metric, c.c) + for name, metrics := range m.histogram { + mf := &dto.MetricFamily{ + Name: newString(name), + Type: dto.MetricType_HISTOGRAM.Enum(), + Metric: make([]*dto.Metric, 0, len(metrics.cs)), + } + for _, c := range metrics.cs { + m := &dto.Metric{} + _ = c.c.Write(m) + fillMetric(m, c.labels) + mf.Metric = append(mf.Metric, m) + } mfs = append(mfs, mf) - return true - }) + } - m.summary.Range(func(_ any, v any) bool { - mc := v.(*prometheusSummary) - mf := m.mfPool.Get() - mf.Name = &mc.name - mf.Type = dto.MetricType_SUMMARY.Enum() - sc := atomic.LoadUint64(&(mc.sampleCount)) - mc.c.Summary.SampleCount = &sc - ss := getFloat64(&(mc.SampleSum)) - mc.c.Summary.SampleSum = &ss - mf.Metric = append(mf.Metric, mc.c) + for name, metrics := range m.summary { + mf := &dto.MetricFamily{ + Name: newString(name), + Type: dto.MetricType_SUMMARY.Enum(), + Metric: make([]*dto.Metric, 0, len(metrics.cs)), + } + for _, c := range metrics.cs { + m := &dto.Metric{} + _ = c.c.Write(m) + fillMetric(m, c.labels) + mf.Metric = append(mf.Metric, m) + } mfs = append(mfs, mf) - return true - }) - - m.mu.Unlock() + } for _, mf := range mfs { _ = enc.Encode(mf) - mf.Reset() - m.mfPool.Put(mf) } if closer, ok := enc.(io.Closer); ok { @@ -344,30 +387,117 @@ func (m *prometheusMeter) Set(opts ...meter.Option) meter.Meter { return nm } -func labelMetric(labels []string) []*dto.LabelPair { - nl := make([]string, len(labels)) - copy(nl, labels) - dtoLabels := make([]*dto.LabelPair, 0, len(nl)/2) - for idx := 0; idx < len(nl); idx += 2 { - dtoLabels = append(dtoLabels, &dto.LabelPair{ - Name: &(nl[idx]), - Value: &(nl[idx+1]), - }) - } - return dtoLabels +type prometheusCounter struct { + c prometheus.Gauge + labels []string } -func newHash(n string, l []string) uint64 { - h := uint64(14695981039346656037) - for i := 0; i < len(n); i++ { - h ^= uint64(n[i]) - h *= 1099511628211 - } - for _, s := range l { - for i := 0; i < len(s); i++ { - h ^= uint64(s[i]) - h *= 1099511628211 - } - } - return h +func (c *prometheusCounter) Add(n int) { + c.c.Add(float64(n)) +} + +func (c *prometheusCounter) Dec() { + c.c.Dec() +} + +func (c *prometheusCounter) Inc() { + c.c.Inc() +} + +func (c *prometheusCounter) Get() uint64 { + m := &dto.Metric{} + if err := c.c.Write(m); err != nil { + return 0 + } + return uint64(m.GetGauge().GetValue()) +} + +func (c *prometheusCounter) Set(n uint64) { + c.c.Set(float64(n)) +} + +type prometheusFloatCounter struct { + c prometheus.Gauge + labels []string +} + +func (c prometheusFloatCounter) Add(n float64) { + c.c.Add(n) +} + +func (c prometheusFloatCounter) Get() float64 { + m := &dto.Metric{} + if err := c.c.Write(m); err != nil { + return 0 + } + return m.GetGauge().GetValue() +} + +func (c prometheusFloatCounter) Set(n float64) { + c.c.Set(n) +} + +func (c prometheusFloatCounter) Sub(n float64) { + c.c.Add(-n) +} + +type prometheusGauge struct { + c prometheus.Gauge + labels []string +} + +func (c prometheusGauge) Get() float64 { + m := &dto.Metric{} + if err := c.c.Write(m); err != nil { + return 0 + } + return float64(m.GetGauge().GetValue()) +} + +type prometheusHistogram struct { + c prometheus.Histogram + labels []string +} + +func (c prometheusHistogram) Reset() { +} + +func (c prometheusHistogram) Update(n float64) { + c.c.Observe(n) +} + +func (c prometheusHistogram) UpdateDuration(n time.Time) { + c.c.Observe(time.Since(n).Seconds()) +} + +type prometheusSummary struct { + c prometheus.Summary + labels []string +} + +func (c prometheusSummary) Update(n float64) { + c.c.Observe(n) +} + +func (c prometheusSummary) UpdateDuration(n time.Time) { + c.c.Observe(time.Since(n).Seconds()) +} + +func newHash(labels []string) uint64 { + h := fnv.New64a() + for _, l := range labels { + h.Write([]byte(l)) + } + return h.Sum64() +} + +func fillMetric(m *dto.Metric, labels []string) *dto.Metric { + m.Label = make([]*dto.LabelPair, 0, len(labels)/2) + for idx := 0; idx < len(labels); idx += 2 { + m.Label = append(m.Label, &dto.LabelPair{ + Name: newString(labels[idx]), + Value: newString(labels[idx+1]), + }) + } + return m } diff --git a/prometheus_test.go b/prometheus_test.go index 2ef2ab6..eed62be 100644 --- a/prometheus_test.go +++ b/prometheus_test.go @@ -2,126 +2,12 @@ package prometheus import ( "bytes" - "context" "fmt" "testing" - "github.com/prometheus/client_golang/prometheus" - dto "github.com/prometheus/client_model/go" - "github.com/prometheus/common/expfmt" - "go.unistack.org/micro/v3/client" - "go.unistack.org/micro/v3/codec" "go.unistack.org/micro/v3/meter" ) -func TestHash(t *testing.T) { - m := NewMeter() // meter.Labels("test_key", "test_val")) - - buf := bytes.NewBuffer(nil) - - for i := 0; i < 100000; i++ { - go func() { - m.Counter("micro_server_request_total", "code", "16", - "endpoint", "/clientprofile.ClientProfileService/GetClientProfile", - "status", "failure").Inc() - m.Counter("micro_server_request_total", "code", "16", - "endpoint", "/clientproduct.ClientProductService/GetDepositProducts", - "status", "failure").Inc() - m.Counter("micro_server_request_total", "code", "16", - "endpoint", "/operationsinfo.OperationsInfoService/GetOperations", - "status", "failure").Inc() - }() - } - _ = m.Write(buf) - t.Logf("h1: %s\n", buf.Bytes()) -} - -func TestHistogram(t *testing.T) { - m := NewMeter() - name := "test" - m.Histogram(name, "endpoint").Update(1) - m.Histogram(name, "endpoint").Update(1) - m.Histogram(name, "endpoint").Update(5) - m.Histogram(name, "endpoint").Update(10) - m.Histogram(name, "endpoint").Update(10) - m.Histogram(name, "endpoint").Update(30) - mbuf := bytes.NewBuffer(nil) - _ = m.Write(mbuf, meter.WriteProcessMetrics(false), meter.WriteFDMetrics(false)) - - /* - if !bytes.Contains(buf.Bytes(), []byte(`micro_server_sum{endpoint="ep1",path="/path1"} 20`)) { - t.Fatalf("invalid metrics output: %s", buf.Bytes()) - } - - if !bytes.Contains(buf.Bytes(), []byte(`micro_server_count{endpoint="ep1",path="/path1"} 2`)) { - t.Fatalf("invalid metrics output: %s", buf.Bytes()) - } - */ - p := prometheus.NewHistogram(prometheus.HistogramOpts{Name: name}) - p.Observe(1) - p.Observe(1) - p.Observe(5) - p.Observe(10) - p.Observe(10) - p.Observe(30) - mdto := &dto.Metric{} - _ = p.Write(mdto) - pbuf := bytes.NewBuffer(nil) - enc := expfmt.NewEncoder(pbuf, expfmt.NewFormat(expfmt.TypeTextPlain)) - mf := &dto.MetricFamily{Name: &name, Type: dto.MetricType_HISTOGRAM.Enum(), Metric: []*dto.Metric{mdto}} - _ = enc.Encode(mf) - - if !bytes.Equal(mbuf.Bytes(), pbuf.Bytes()) { - fmt.Printf("m\n%s\n", mbuf.Bytes()) - fmt.Printf("m\n%s\n", pbuf.Bytes()) - } -} - -func TestSummary(t *testing.T) { - t.Skip() - name := "micro_server" - m := NewMeter() - m.Summary("micro_server").Update(1) - m.Summary("micro_server").Update(1) - m.Summary("micro_server").Update(5) - m.Summary("micro_server").Update(10) - m.Summary("micro_server").Update(10) - m.Summary("micro_server").Update(30) - mbuf := bytes.NewBuffer(nil) - _ = m.Write(mbuf, meter.WriteProcessMetrics(false), meter.WriteFDMetrics(false)) - - if !bytes.Contains(mbuf.Bytes(), []byte(`micro_server_sum 57`)) { - t.Fatalf("invalid metrics output: %s", mbuf.Bytes()) - } - - if !bytes.Contains(mbuf.Bytes(), []byte(`micro_server_count 6`)) { - t.Fatalf("invalid metrics output: %s", mbuf.Bytes()) - } - - objectives := make(map[float64]float64) - for _, c := range meter.DefaultSummaryQuantiles { - objectives[c] = c - } - p := prometheus.NewSummary(prometheus.SummaryOpts{Name: name, Objectives: objectives, MaxAge: meter.DefaultSummaryWindow}) - p.Observe(1) - p.Observe(1) - p.Observe(5) - p.Observe(10) - p.Observe(10) - p.Observe(30) - mdto := &dto.Metric{} - _ = p.Write(mdto) - pbuf := bytes.NewBuffer(nil) - enc := expfmt.NewEncoder(pbuf, expfmt.NewFormat(expfmt.TypeTextPlain)) - mf := &dto.MetricFamily{Name: &name, Type: dto.MetricType_SUMMARY.Enum(), Metric: []*dto.Metric{mdto}} - _ = enc.Encode(mf) - - if !bytes.Equal(mbuf.Bytes(), pbuf.Bytes()) { - fmt.Printf("m\n%s\n", mbuf.Bytes()) - fmt.Printf("m\n%s\n", pbuf.Bytes()) - } -} - func TestStd(t *testing.T) { m := NewMeter(meter.WriteProcessMetrics(true), meter.WriteFDMetrics(true)) if err := m.Init(); err != nil { @@ -134,24 +20,16 @@ func TestStd(t *testing.T) { } } -func TestWrapper(t *testing.T) { - m := NewMeter() // meter.Labels("test_key", "test_val")) - - ctx := context.Background() - - c := client.NewClient(client.Meter(m)) - if err := c.Init(); err != nil { - t.Fatal(err) - } - rsp := &codec.Frame{} - req := &codec.Frame{} - err := c.Call(ctx, c.NewRequest("svc2", "Service.Method", req), rsp) - _, _ = rsp, err - buf := bytes.NewBuffer(nil) - _ = m.Write(buf, meter.WriteProcessMetrics(false), meter.WriteFDMetrics(false)) - if !bytes.Contains(buf.Bytes(), []byte(`micro_client_request_inflight{endpoint="Service.Method"} 0`)) { - t.Fatalf("invalid metrics output: %s", buf.Bytes()) +func TestBuildName(t *testing.T) { + m := NewMeter() + check := `micro_foo{aaa="b",bar="baz",ccc="d"}` + name := m.buildMetric("micro_foo", "bar", "baz", "aaa", "b", "ccc", "d") + if name != check { + t.Fatalf("metric name error: %s != %s", name, check) } + + cnt := m.Counter("counter", "key", "val") + cnt.Inc() } func TestMultiple(t *testing.T) { @@ -168,9 +46,31 @@ func TestMultiple(t *testing.T) { buf := bytes.NewBuffer(nil) _ = m.Write(buf, meter.WriteProcessMetrics(false), meter.WriteFDMetrics(false)) if !bytes.Contains(buf.Bytes(), []byte(`micro_server{endpoint="ep1",path="/path1"} 2`)) { - t.Fatalf("invalid metrics output: %s", buf.Bytes()) - } - if !bytes.Contains(buf.Bytes(), []byte(`micro_server{endpoint="ep3",path="/path3",status="success"} 1`)) { + // t.Fatal("XXXX") t.Fatalf("invalid metrics output: %s", buf.Bytes()) } } + +func TestCounterSet(t *testing.T) { + m := NewMeter() + + value := uint64(42) + + m.Counter("forte_accounts_total", "channel_code", "crm").Set(value) + + fmt.Println(uint64(float64(value))) + + buf := bytes.NewBuffer(nil) + + _ = m.Write(buf) + + output := buf.String() + + fmt.Println(output) + + expectedOutput := fmt.Sprintf(`%s{channel_code="crm"} %d`, "forte_accounts_total", value) + + if !bytes.Contains(buf.Bytes(), []byte(expectedOutput)) { + t.Fatalf("invalid metrics output: expected %q, got %q", expectedOutput, output) + } +} diff --git a/summary.go b/summary.go deleted file mode 100644 index b73d740..0000000 --- a/summary.go +++ /dev/null @@ -1,26 +0,0 @@ -package prometheus - -import ( - "sync/atomic" - "time" - - dto "github.com/prometheus/client_model/go" -) - -type prometheusSummary struct { - name string - c *dto.Metric - sampleCount uint64 - SampleSum float64 -} - -func (c *prometheusSummary) Update(n float64) { - atomic.AddUint64(&(c.sampleCount), 1) - addFloat64(&(c.SampleSum), n) -} - -func (c *prometheusSummary) UpdateDuration(t time.Time) { - n := time.Since(t).Seconds() - atomic.AddUint64(&(c.sampleCount), 1) - addFloat64(&(c.SampleSum), n) -}