From da9201efff834a7d93553bb8e5714e737a93ef4e Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Sun, 6 Oct 2024 13:45:33 +0300 Subject: [PATCH] lower memory usage Signed-off-by: Vasiliy Tolstov --- counter.go | 91 +++++++++ gauge.go | 12 ++ go.mod | 10 +- go.sum | 10 +- histogram.go | 39 ++++ prometheus.go | 493 +++++++++++++++------------------------------ prometheus_test.go | 93 ++++++++- summary.go | 24 +++ 8 files changed, 424 insertions(+), 348 deletions(-) create mode 100644 counter.go create mode 100644 gauge.go create mode 100644 histogram.go create mode 100644 summary.go diff --git a/counter.go b/counter.go new file mode 100644 index 0000000..d5bc663 --- /dev/null +++ b/counter.go @@ -0,0 +1,91 @@ +package prometheus + +import ( + "math" + "sync/atomic" + "unsafe" + + dto "github.com/prometheus/client_model/go" +) + +type prometheusCounter struct { + name string + c *dto.Metric +} + +func (c *prometheusCounter) Add(n int) { + addFloat64(c.c.Gauge.Value, float64(n)) +} + +func (c *prometheusCounter) Dec() { + addFloat64(c.c.Gauge.Value, float64(-1)) +} + +func (c *prometheusCounter) Inc() { + addFloat64(c.c.Gauge.Value, float64(1)) +} + +func (c *prometheusCounter) Get() uint64 { + return uint64(getFloat64(c.c.Gauge.Value)) +} + +func (c *prometheusCounter) Set(n uint64) { + setFloat64(c.c.Gauge.Value, math.Float64frombits(n)) +} + +type prometheusFloatCounter struct { + name string + c *dto.Metric +} + +func (c *prometheusFloatCounter) Add(n float64) { + addFloat64(c.c.Gauge.Value, n) +} + +func (c *prometheusFloatCounter) Dec() { + addFloat64(c.c.Gauge.Value, float64(-1)) +} + +func (c *prometheusFloatCounter) Inc() { + addFloat64(c.c.Gauge.Value, float64(1)) +} + +func (c *prometheusFloatCounter) Get() float64 { + return getFloat64(c.c.Gauge.Value) +} + +func (c *prometheusFloatCounter) Set(n float64) { + setFloat64(c.c.Gauge.Value, n) +} + +func (c *prometheusFloatCounter) Sub(n float64) { + addFloat64(c.c.Gauge.Value, -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 new file mode 100644 index 0000000..783992d --- /dev/null +++ b/gauge.go @@ -0,0 +1,12 @@ +package prometheus + +import dto "github.com/prometheus/client_model/go" + +type prometheusGauge struct { + name string + c *dto.Metric +} + +func (c prometheusGauge) Get() float64 { + return getFloat64(c.c.Gauge.Value) +} diff --git a/go.mod b/go.mod index 3633f0b..2af65d6 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,13 @@ module go.unistack.org/micro-meter-prometheus/v3 -go 1.21 - -toolchain go1.22.0 +go 1.22.0 require ( github.com/prometheus/client_golang v1.20.4 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.34.2 ) require ( @@ -19,7 +18,6 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect go.unistack.org/micro-proto/v3 v3.4.1 // indirect golang.org/x/sys v0.25.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/grpc v1.57.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.58.2 // indirect ) diff --git a/go.sum b/go.sum index 4527d5c..d9856c5 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,6 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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.90 h1:EE7t4J14yjZC/lW6e0BfiBnU/xt/KqGkqSxumJhI/fI= -go.unistack.org/micro/v3 v3.10.90/go.mod h1:erMgt3Bl7vQQ0e9UpQyR5NlLiZ9pKeEJ9+1tfYFaqUg= 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.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= @@ -36,10 +34,10 @@ golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/histogram.go b/histogram.go new file mode 100644 index 0000000..61e746b --- /dev/null +++ b/histogram.go @@ -0,0 +1,39 @@ +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 02d1d49..077a67f 100644 --- a/prometheus.go +++ b/prometheus.go @@ -2,7 +2,6 @@ package prometheus import ( "fmt" - "hash/fnv" "io" "regexp" "sync" @@ -13,6 +12,8 @@ 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) @@ -25,27 +26,7 @@ type prometheusMeter struct { gauge *sync.Map histogram *sync.Map summary *sync.Map - sync.Mutex -} - -type counters struct { - cs *sync.Map -} - -type gauges struct { - cs *sync.Map -} - -type histograms struct { - cs *sync.Map -} - -type summaries struct { - cs *sync.Map -} - -type floatCounters struct { - cs *sync.Map + mfPool xpool.Pool[*dto.MetricFamily] } func NewMeter(opts ...meter.Option) *prometheusMeter { @@ -57,6 +38,9 @@ func NewMeter(opts ...meter.Option) *prometheusMeter { gauge: &sync.Map{}, histogram: &sync.Map{}, summary: &sync.Map{}, + mfPool: xpool.NewPool[*dto.MetricFamily](func() *dto.MetricFamily { + return &dto.MetricFamily{} + }), } } @@ -65,161 +49,134 @@ func (m *prometheusMeter) Name() string { } func (m *prometheusMeter) Counter(name string, labels ...string) meter.Counter { - m.Lock() - defer m.Unlock() - labels = append(m.opts.Labels, labels...) - vcd, ok := m.counter.Load(name) - h := newHash(labels) + clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) + h := newHash(name, clabels) + mc, ok := m.counter.Load(h) if !ok { - cd := &counters{cs: &sync.Map{}} - c := &prometheusCounter{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: name}), labels: labels} - cd.cs.Store(h, c) - m.counter.Store(name, cd) - return c + var v float64 + mc = &prometheusCounter{ + name: name, + c: &dto.Metric{ + Gauge: &dto.Gauge{Value: &v}, + Label: labelMetric(clabels), + }, + } + m.counter.Store(h, mc) } - cd := vcd.(*counters) - vc, ok := cd.cs.Load(h) - if !ok { - c := &prometheusCounter{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: name}), labels: labels} - cd.cs.Store(h, c) - m.counter.Store(name, cd) - return c - } - c := vc.(*prometheusCounter) - return c + return mc.(*prometheusCounter) } func (m *prometheusMeter) FloatCounter(name string, labels ...string) meter.FloatCounter { - m.Lock() - defer m.Unlock() - labels = append(m.opts.Labels, labels...) - vcd, ok := m.floatCounter.Load(name) - h := newHash(labels) + clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) + h := newHash(name, clabels) + mc, ok := m.floatCounter.Load(h) if !ok { - cd := &floatCounters{cs: &sync.Map{}} - c := &prometheusFloatCounter{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: name}), labels: labels} - cd.cs.Store(h, c) - m.floatCounter.Store(name, cd) - return c + var v float64 + mc = &prometheusFloatCounter{ + name: name, + c: &dto.Metric{ + Gauge: &dto.Gauge{Value: &v}, + Label: labelMetric(clabels), + }, + } + m.floatCounter.Store(h, mc) } - cd := vcd.(*floatCounters) - vc, ok := cd.cs.Load(h) - if !ok { - c := &prometheusFloatCounter{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: name}), labels: labels} - cd.cs.Store(h, c) - m.floatCounter.Store(name, cd) - return c - } - c := vc.(*prometheusFloatCounter) - return c + return mc.(*prometheusFloatCounter) } func (m *prometheusMeter) Gauge(name string, fn func() float64, labels ...string) meter.Gauge { - m.Lock() - defer m.Unlock() - labels = append(m.opts.Labels, labels...) - vcd, ok := m.gauge.Load(name) - h := newHash(labels) + clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) + h := newHash(name, clabels) + mc, ok := m.gauge.Load(h) if !ok { - cd := &gauges{cs: &sync.Map{}} - c := &prometheusGauge{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: name}), labels: labels} - cd.cs.Store(h, c) - m.gauge.Store(name, cd) - return c + var v float64 + mc = &prometheusGauge{ + name: name, + c: &dto.Metric{ + Gauge: &dto.Gauge{Value: &v}, + Label: labelMetric(clabels), + }, + } + m.gauge.Store(h, mc) } - cd := vcd.(*gauges) - vc, ok := cd.cs.Load(h) - if !ok { - c := &prometheusGauge{c: prometheus.NewGauge(prometheus.GaugeOpts{Name: name}), labels: labels} - cd.cs.Store(h, c) - m.gauge.Store(name, cd) - return c - } - c := vc.(*prometheusGauge) - return c + return mc.(*prometheusGauge) } func (m *prometheusMeter) Histogram(name string, labels ...string) meter.Histogram { - m.Lock() - defer m.Unlock() - labels = append(m.opts.Labels, labels...) - vcd, ok := m.histogram.Load(name) - h := newHash(labels) + clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) + h := newHash(name, clabels) + mc, ok := m.histogram.Load(h) if !ok { - cd := &histograms{cs: &sync.Map{}} - c := &prometheusHistogram{c: prometheus.NewHistogram(prometheus.HistogramOpts{Name: name}), labels: labels} - cd.cs.Store(h, c) - m.histogram.Store(name, cd) - return c + var c uint64 + var s float64 + buckets := make([]float64, len(prometheus.DefBuckets)) + copy(buckets, prometheus.DefBuckets) + mdto := &dto.Metric{ + Histogram: &dto.Histogram{ + SampleCount: &c, + 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} + } + mc = &prometheusHistogram{ + name: name, + c: mdto, + } + + m.histogram.Store(h, mc) } - cd := vcd.(*histograms) - vc, ok := cd.cs.Load(h) - if !ok { - c := &prometheusHistogram{c: prometheus.NewHistogram(prometheus.HistogramOpts{Name: name}), labels: labels} - cd.cs.Store(h, c) - m.histogram.Store(name, cd) - return c - } - c := vc.(*prometheusHistogram) - return c + return mc.(*prometheusHistogram) } func (m *prometheusMeter) Summary(name string, labels ...string) meter.Summary { - m.Lock() - defer m.Unlock() - labels = append(m.opts.Labels, labels...) - vcd, ok := m.summary.Load(name) - h := newHash(labels) + clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) + h := newHash(name, clabels) + mc, ok := m.summary.Load(h) if !ok { - cd := &summaries{cs: &sync.Map{}} - c := &prometheusSummary{c: prometheus.NewSummary(prometheus.SummaryOpts{Name: name}), labels: labels} - cd.cs.Store(h, c) - m.summary.Store(name, cd) - return c + var c uint64 + var s float64 + mc = &prometheusSummary{ + name: name, + c: &dto.Metric{ + Summary: &dto.Summary{ + SampleCount: &c, + SampleSum: &s, + CreatedTimestamp: timestamppb.Now(), + }, + Label: labelMetric(clabels), + }, + } + m.summary.Store(h, mc) } - cd := vcd.(*summaries) - vc, ok := cd.cs.Load(h) - if !ok { - c := &prometheusSummary{c: prometheus.NewSummary(prometheus.SummaryOpts{Name: name}), labels: labels} - cd.cs.Store(h, c) - m.summary.Store(name, cd) - return c - } - c := vc.(*prometheusSummary) - return c + return mc.(*prometheusSummary) } func (m *prometheusMeter) SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) meter.Summary { - m.Lock() - defer m.Unlock() - labels = append(m.opts.Labels, labels...) - vcd, ok := m.summary.Load(name) - h := newHash(labels) + clabels := meter.BuildLabels(append(m.opts.Labels, labels...)...) + h := newHash(name, clabels) + mc, ok := m.summary.Load(h) if !ok { - cd := &summaries{cs: &sync.Map{}} - c := &prometheusSummary{c: prometheus.NewSummary(prometheus.SummaryOpts{ - Name: name, - MaxAge: window, - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }), labels: labels} - cd.cs.Store(h, c) - m.summary.Store(name, cd) - return c + var c uint64 + var s float64 + mc = &prometheusSummary{ + name: name, + c: &dto.Metric{ + Summary: &dto.Summary{ + SampleCount: &c, + SampleSum: &s, + }, + Label: labelMetric(clabels), + }, + } + m.summary.Store(h, mc) } - cd := vcd.(*summaries) - vc, ok := cd.cs.Load(h) - if !ok { - c := &prometheusSummary{c: prometheus.NewSummary(prometheus.SummaryOpts{ - Name: name, - MaxAge: window, - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }), labels: labels} - cd.cs.Store(h, c) - m.summary.Store(name, cd) - return c - } - c := vc.(*prometheusSummary) - return c + return mc.(*prometheusSummary) } func (m *prometheusMeter) Init(opts ...meter.Option) error { @@ -256,97 +213,59 @@ func (m *prometheusMeter) Write(w io.Writer, opts ...meter.Option) error { enc := expfmt.NewEncoder(w, expfmt.NewFormat(expfmt.TypeTextPlain)) m.counter.Range(func(k, v any) bool { - name := k.(string) - mf := &dto.MetricFamily{ - Name: &name, - Type: dto.MetricType_GAUGE.Enum(), - } - v.(*counters).cs.Range(func(_, nv any) bool { - c := nv.(*prometheusCounter) - m := &dto.Metric{} - _ = c.c.Write(m) - fillMetric(m, c.labels) - mf.Metric = append(mf.Metric, m) - return true - }) - mfs = append(mfs, mf) - return true - }) - - m.gauge.Range(func(k, v any) bool { - name := k.(string) - mf := &dto.MetricFamily{ - Name: &name, - Type: dto.MetricType_GAUGE.Enum(), - } - v.(*gauges).cs.Range(func(_, nv any) bool { - c := nv.(*prometheusGauge) - m := &dto.Metric{} - _ = c.c.Write(m) - fillMetric(m, c.labels) - mf.Metric = append(mf.Metric, m) - return true - }) + c := v.(*prometheusCounter) + mf := m.mfPool.Get() + mf.Name = &c.name + mf.Type = dto.MetricType_GAUGE.Enum() + mf.Metric = append(mf.Metric, c.c) mfs = append(mfs, mf) return true }) m.floatCounter.Range(func(k, v any) bool { - name := k.(string) - mf := &dto.MetricFamily{ - Name: &name, - Type: dto.MetricType_GAUGE.Enum(), - } - v.(*floatCounters).cs.Range(func(_, nv any) bool { - c := nv.(*prometheusFloatCounter) - m := &dto.Metric{} - _ = c.c.Write(m) - fillMetric(m, c.labels) - mf.Metric = append(mf.Metric, m) - return true - }) + c := v.(*prometheusFloatCounter) + mf := m.mfPool.Get() + mf.Name = &c.name + mf.Type = dto.MetricType_GAUGE.Enum() + mf.Metric = append(mf.Metric, c.c) + mfs = append(mfs, mf) + return true + }) + + m.gauge.Range(func(k, v any) bool { + c := v.(*prometheusGauge) + mf := m.mfPool.Get() + mf.Name = &c.name + mf.Type = dto.MetricType_GAUGE.Enum() + mf.Metric = append(mf.Metric, c.c) mfs = append(mfs, mf) return true }) m.histogram.Range(func(k, v any) bool { - name := k.(string) - mf := &dto.MetricFamily{ - Name: &name, - Type: dto.MetricType_HISTOGRAM.Enum(), - } - v.(*histograms).cs.Range(func(_, nv any) bool { - c := nv.(*prometheusHistogram) - m := &dto.Metric{} - _ = c.c.Write(m) - fillMetric(m, c.labels) - mf.Metric = append(mf.Metric, m) - return true - }) + c := v.(*prometheusHistogram) + mf := m.mfPool.Get() + mf.Name = &c.name + mf.Type = dto.MetricType_HISTOGRAM.Enum() + mf.Metric = append(mf.Metric, c.c) mfs = append(mfs, mf) return true }) m.summary.Range(func(k, v any) bool { - name := k.(string) - mf := &dto.MetricFamily{ - Name: &name, - Type: dto.MetricType_SUMMARY.Enum(), - } - v.(*summaries).cs.Range(func(_, nv any) bool { - c := nv.(*prometheusSummary) - m := &dto.Metric{} - _ = c.c.Write(m) - fillMetric(m, c.labels) - mf.Metric = append(mf.Metric, m) - return true - }) + c := v.(*prometheusSummary) + mf := m.mfPool.Get() + mf.Name = &c.name + mf.Type = dto.MetricType_SUMMARY.Enum() + mf.Metric = append(mf.Metric, c.c) mfs = append(mfs, mf) return true }) for _, mf := range mfs { _ = enc.Encode(mf) + mf.Reset() + m.mfPool.Put(mf) } if closer, ok := enc.(io.Closer); ok { @@ -390,124 +309,28 @@ func (m *prometheusMeter) Set(opts ...meter.Option) meter.Meter { return nm } -type prometheusCounter struct { - c prometheus.Gauge - labels []string -} - -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 { - labels = meter.BuildLabels(labels...) - h := fnv.New64a() - for _, l := range labels { - h.Write([]byte(l)) - } - return h.Sum64() -} - -func fillMetric(m *dto.Metric, labels []string) *dto.Metric { - var ok bool - seen := make(map[string]bool, len(labels)/2) - m.Label = make([]*dto.LabelPair, 0, len(labels)/2) +func labelMetric(labels []string) []*dto.LabelPair { + dtoLabels := make([]*dto.LabelPair, 0, len(labels)/2) for idx := 0; idx < len(labels); idx += 2 { - if _, ok = seen[labels[idx]]; ok { - continue - } - m.Label = append(m.Label, &dto.LabelPair{ + dtoLabels = append(dtoLabels, &dto.LabelPair{ Name: &(labels[idx]), Value: &(labels[idx+1]), }) - seen[labels[idx]] = true } - return m + return dtoLabels +} + +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 } diff --git a/prometheus_test.go b/prometheus_test.go index 6d1d67c..81b777f 100644 --- a/prometheus_test.go +++ b/prometheus_test.go @@ -3,13 +3,102 @@ 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 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) { + 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 { @@ -56,7 +145,9 @@ 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.Fatal("XXXX") + t.Fatalf("invalid metrics output: %s", buf.Bytes()) + } + if !bytes.Contains(buf.Bytes(), []byte(`micro_server{endpoint="ep3",path="/path3",status="success"} 1`)) { t.Fatalf("invalid metrics output: %s", buf.Bytes()) } } diff --git a/summary.go b/summary.go new file mode 100644 index 0000000..58bb81d --- /dev/null +++ b/summary.go @@ -0,0 +1,24 @@ +package prometheus + +import ( + "sync/atomic" + "time" + + dto "github.com/prometheus/client_model/go" +) + +type prometheusSummary struct { + name string + c *dto.Metric +} + +func (c prometheusSummary) Update(n float64) { + atomic.AddUint64(c.c.Summary.SampleCount, 1) + addFloat64(c.c.Summary.SampleSum, n) +} + +func (c prometheusSummary) UpdateDuration(n time.Time) { + x := time.Since(n).Seconds() + atomic.AddUint64(c.c.Summary.SampleCount, 1) + addFloat64(c.c.Summary.SampleSum, x) +}