3 Commits

Author SHA1 Message Date
vtolstov
43ab6e361e Apply Code Coverage Badge 2025-10-28 14:04:50 +00:00
1b817d6d01 Merge pull request 'move v4 changes' (#118) from devstigneev/micro-meter-victoriametrics:v3 into v3
All checks were successful
coverage / build (push) Successful in 1m53s
test / test (push) Successful in 4m5s
Reviewed-on: #118
2025-10-28 17:03:23 +03:00
Evstigneev Denis
6d6d3e0855 move v4 changes
Some checks failed
coverage / build (pull_request) Has been cancelled
lint / lint (pull_request) Has been cancelled
test / test (pull_request) Has been cancelled
2025-10-28 17:00:45 +03:00
6 changed files with 200 additions and 76 deletions

39
.gitignore vendored Normal file
View File

@@ -0,0 +1,39 @@
# Develop tools
/.vscode/
/.idea/
.idea
.vscode
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Folders
_obj
_test
_build
.DS_Store
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# vim temp files
*~
*.swp
*.swo

View File

@@ -1,5 +1,5 @@
# VictoriaMetrics # VictoriaMetrics
![Coverage](https://img.shields.io/badge/Coverage-39.2%25-yellow) ![Coverage](https://img.shields.io/badge/Coverage-45.5%25-yellow)
Wrappers are a form of middleware that can be used with go-micro services. They can wrap both the Client and Server handlers. Wrappers are a form of middleware that can be used with go-micro services. They can wrap both the Client and Server handlers.
This plugin implements the HandlerWrapper interface to provide automatic prometheus metric handling This plugin implements the HandlerWrapper interface to provide automatic prometheus metric handling

19
go.mod
View File

@@ -1,16 +1,23 @@
module go.unistack.org/micro-meter-victoriametrics/v3 module go.unistack.org/micro-meter-victoriametrics/v3
go 1.22 go 1.24.0
toolchain go1.23.2
require ( require (
go.unistack.org/metrics v0.0.1 github.com/VictoriaMetrics/metrics v1.40.2
go.unistack.org/micro/v3 v3.10.100 go.unistack.org/micro/v3 v3.11.48
) )
require ( require (
github.com/ash3in/uuidv8 v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/matoous/go-nanoid v1.5.1 // indirect
github.com/valyala/fastrand v1.1.0 // indirect github.com/valyala/fastrand v1.1.0 // indirect
github.com/valyala/histogram v1.2.0 // indirect github.com/valyala/histogram v1.2.0 // indirect
golang.org/x/sys v0.27.0 // indirect go.unistack.org/micro-proto/v3 v3.4.1 // indirect
golang.org/x/sys v0.28.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect
google.golang.org/grpc v1.69.2 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

50
go.sum
View File

@@ -1,10 +1,48 @@
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/VictoriaMetrics/metrics v1.40.2 h1:OVSjKcQEx6JAwGeu8/KQm9Su5qJ72TMEW4xYn5vw3Ac=
github.com/VictoriaMetrics/metrics v1.40.2/go.mod h1:XE4uudAAIRaJE614Tl5HMrtoEU6+GDZO4QTnNSsZRuA=
github.com/ash3in/uuidv8 v1.2.0 h1:2oogGdtCPwaVtyvPPGin4TfZLtOGE5F+W++E880G6SI=
github.com/ash3in/uuidv8 v1.2.0/go.mod h1:BnU0wJBxnzdEKmVg4xckBkD+VZuecTFTUP3M0dWgyY4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/matoous/go-nanoid v1.5.1 h1:aCjdvTyO9LLnTIi0fgdXhOPPvOHjpXN6Ik9DaNjIct4=
github.com/matoous/go-nanoid v1.5.1/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= 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 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
go.unistack.org/metrics v0.0.1 h1:sCnGO059ZccGC/D34iRH121eSk+7ci5+OY9cl5K7GKY= go.unistack.org/micro-proto/v3 v3.4.1 h1:UTjLSRz2YZuaHk9iSlVqqsA50JQNAEK2ZFboGqtEa9Q=
go.unistack.org/metrics v0.0.1/go.mod h1:1FY4R7EKJa9Oz2D6wlGScNerpl6igRs9Cx/3et4Rgs4= go.unistack.org/micro-proto/v3 v3.4.1/go.mod h1:okx/cnOhzuCX0ggl/vToatbCupi0O44diiiLLsZ93Zo=
go.unistack.org/micro/v3 v3.10.100 h1:yWOaU0ImCGm5k5MUzlIobJUOr+KLfrR/BoDZvcHyKxM= go.unistack.org/micro/v3 v3.11.48 h1:lHJYSHU2z1TTcuswItGwG7cZXN6n04EFqY7lk/0gA7w=
go.unistack.org/micro/v3 v3.10.100/go.mod h1:YzMldzHN9Ei+zy5t/Psu7RUWDZwUfrNYiStSQtTz90g= go.unistack.org/micro/v3 v3.11.48/go.mod h1:fDQ8Mu9wubaFP0L8hNQlpzHiEnWN0wbOlawN9HYo0N4=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -4,7 +4,7 @@ import (
"io" "io"
"time" "time"
"go.unistack.org/metrics" "github.com/VictoriaMetrics/metrics"
"go.unistack.org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
) )
@@ -14,119 +14,129 @@ type victoriametricsMeter struct {
prometheusCompat bool prometheusCompat bool
} }
type prometheusCompatKey struct{}
func PrometheusCompat(b bool) meter.Option {
return meter.SetOption(prometheusCompatKey{}, b)
}
func NewMeter(opts ...meter.Option) meter.Meter { func NewMeter(opts ...meter.Option) meter.Meter {
m := &victoriametricsMeter{set: metrics.NewSet(), opts: meter.NewOptions(opts...)} m := &victoriametricsMeter{set: metrics.NewSet(), opts: meter.NewOptions(opts...)}
if v, ok := m.opts.Context.Value(prometheusCompatKey{}).(bool); ok { if v, ok := m.opts.Context.Value(prometheusCompatKey{}).(bool); ok && v {
m.prometheusCompat = v m.prometheusCompat = v
} }
return m return m
} }
func (m *victoriametricsMeter) Name() string { func (r *victoriametricsMeter) Name() string {
return m.opts.Name return r.opts.Name
} }
func (m *victoriametricsMeter) Clone(opts ...meter.Option) meter.Meter { func (r *victoriametricsMeter) Unregister(name string, labels ...string) bool {
options := m.opts return r.set.UnregisterMetric(r.buildName(name, labels...))
}
func (r *victoriametricsMeter) Clone(opts ...meter.Option) meter.Meter {
options := r.opts
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
nm := &victoriametricsMeter{set: m.set, opts: options, prometheusCompat: m.prometheusCompat} m := &victoriametricsMeter{set: r.set, opts: options}
if v, ok := m.opts.Context.Value(prometheusCompatKey{}).(bool); ok { if v, ok := m.opts.Context.Value(prometheusCompatKey{}).(bool); ok && v {
m.prometheusCompat = v m.prometheusCompat = v
} }
return nm return m
} }
func (m *victoriametricsMeter) buildName(name string, labels ...string) string { func (r *victoriametricsMeter) buildName(name string, labels ...string) string {
nl := len(m.opts.Labels) + len(labels) nl := len(r.opts.Labels) + len(labels)
if nl == 0 { if nl == 0 {
return name return name
} }
nlabels := make([]string, 0, nl) nlabels := make([]string, 0, nl)
nlabels = append(nlabels, m.opts.Labels...) nlabels = append(nlabels, r.opts.Labels...)
nlabels = append(nlabels, labels...) nlabels = append(nlabels, labels...)
return meter.BuildName(name, nlabels...) return meter.BuildName(name, nlabels...)
} }
func (m *victoriametricsMeter) Counter(name string, labels ...string) meter.Counter { func (r *victoriametricsMeter) Counter(name string, labels ...string) meter.Counter {
return m.set.GetOrCreateCounter(m.buildName(name, labels...)) return r.set.GetOrCreateCounter(r.buildName(name, labels...))
} }
func (m *victoriametricsMeter) FloatCounter(name string, labels ...string) meter.FloatCounter { func (r *victoriametricsMeter) FloatCounter(name string, labels ...string) meter.FloatCounter {
return m.set.GetOrCreateFloatCounter(m.buildName(name, labels...)) return r.set.GetOrCreateFloatCounter(r.buildName(name, labels...))
} }
func (m *victoriametricsMeter) Gauge(name string, f func() float64, labels ...string) meter.Gauge { func (r *victoriametricsMeter) Gauge(name string, f func() float64, labels ...string) meter.Gauge {
return m.set.GetOrCreateGauge(m.buildName(name, labels...), f) return r.set.GetOrCreateGauge(r.buildName(name, labels...), f)
} }
func (m *victoriametricsMeter) Histogram(name string, labels ...string) meter.Histogram { func (r *victoriametricsMeter) Histogram(name string, labels ...string) meter.Histogram {
if m.prometheusCompat { if r.prometheusCompat {
return m.set.GetOrCreateCompatibleHistogram(m.buildName(name, labels...)) return r.set.GetOrCreatePrometheusHistogramExt(r.buildName(name, labels...), r.opts.Quantiles)
} }
return m.set.GetOrCreateHistogram(m.buildName(name, labels...)) return r.set.GetOrCreateHistogram(r.buildName(name, labels...))
} }
func (m *victoriametricsMeter) Summary(name string, labels ...string) meter.Summary { func (r *victoriametricsMeter) HistogramExt(name string, quantiles []float64, labels ...string) meter.Histogram {
return m.set.GetOrCreateSummary(m.buildName(name, labels...)) if r.prometheusCompat {
} return r.set.GetOrCreatePrometheusHistogramExt(r.buildName(name, labels...), quantiles)
func (m *victoriametricsMeter) SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) meter.Summary {
return m.set.GetOrCreateSummaryExt(m.buildName(name, labels...), window, quantiles)
}
func (m *victoriametricsMeter) Set(opts ...meter.Option) meter.Meter {
nm := &victoriametricsMeter{opts: m.opts}
for _, o := range opts {
o(&nm.opts)
} }
nm.set = metrics.NewSet() return r.set.GetOrCreateHistogram(r.buildName(name, labels...))
if v, ok := nm.opts.Context.Value(prometheusCompatKey{}).(bool); ok {
nm.prometheusCompat = v
}
return nm
} }
func (m *victoriametricsMeter) Init(opts ...meter.Option) error { func (r *victoriametricsMeter) Summary(name string, labels ...string) meter.Summary {
return r.set.GetOrCreateSummary(r.buildName(name, labels...))
}
func (r *victoriametricsMeter) SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) meter.Summary {
return r.set.GetOrCreateSummaryExt(r.buildName(name, labels...), window, quantiles)
}
func (r *victoriametricsMeter) Set(opts ...meter.Option) meter.Meter {
m := &victoriametricsMeter{opts: r.opts, prometheusCompat: r.prometheusCompat}
for _, o := range opts { for _, o := range opts {
o(&m.opts) o(&m.opts)
} }
if v, ok := m.opts.Context.Value(prometheusCompatKey{}).(bool); ok { m.set = metrics.NewSet()
m.prometheusCompat = v return m
}
func (r *victoriametricsMeter) Init(opts ...meter.Option) error {
for _, o := range opts {
o(&r.opts)
}
if v, ok := r.opts.Context.Value(prometheusCompatKey{}).(bool); ok && v {
r.prometheusCompat = v
}
if r.opts.Quantiles == nil {
r.opts.Quantiles = meter.DefaultQuantiles
} }
return nil return nil
} }
func (m *victoriametricsMeter) Write(w io.Writer, opts ...meter.Option) error { func (r *victoriametricsMeter) Write(w io.Writer, opts ...meter.Option) error {
options := m.opts options := r.opts
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
m.set.WritePrometheus(w) r.set.WritePrometheus(w)
if options.WriteProcessMetrics { if options.WriteProcessMetrics {
metrics.WriteProcessMetrics(w) metrics.WriteProcessMetrics(w)
} }
if options.WriteFDMetrics { if options.WriteFDMetrics {
metrics.WriteFDMetrics(w) metrics.WriteFDMetrics(w)
} }
return nil return nil
} }
func (m *victoriametricsMeter) Options() meter.Options { func (r *victoriametricsMeter) Options() meter.Options {
return m.opts return r.opts
} }
func (m *victoriametricsMeter) String() string { func (r *victoriametricsMeter) String() string {
return "victoriametrics" return "victoriametrics"
} }
type prometheusCompatKey struct{}
func PrometheusCompat(b bool) meter.Option {
return meter.SetOption(prometheusCompatKey{}, b)
}

View File

@@ -2,14 +2,32 @@ package victoriametrics
import ( import (
"bytes" "bytes"
"context"
"testing" "testing"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/meter"
) )
func BenchmarkBuildName(b *testing.B) {
m := NewMeter(meter.Labels("pod", "xxx"))
if err := m.Init(); err != nil {
b.Fatal(err)
}
im := m.(*victoriametricsMeter)
b.ResetTimer()
for i := 0; i < b.N; i++ {
name := im.buildName("micro_foo", "bar", "baz", "aaa", "b", "ccc", "d")
_ = name
}
}
func TestBuildName(t *testing.T) { func TestBuildName(t *testing.T) {
m := NewMeter() m := NewMeter()
im := m.(*victoriametricsMeter) im := m.(*victoriametricsMeter)
check := `micro_foo{micro_aaa="b",micro_bar="baz",micro_ccc="d"}` check := `micro_foo{bar="baz",aaa="b",ccc="d"}`
name := im.buildName("micro_foo", "micro_bar", "baz", "micro_aaa", "b", "micro_ccc", "d") name := im.buildName("micro_foo", "bar", "baz", "aaa", "b", "ccc", "d")
if name != check { if name != check {
t.Fatalf("metric name error: %s != %s", name, check) t.Fatalf("metric name error: %s != %s", name, check)
} }
@@ -18,12 +36,24 @@ func TestBuildName(t *testing.T) {
cnt.Inc() cnt.Inc()
} }
func TestPrometheusCompat(t *testing.T) { func TestWrapper(t *testing.T) {
m := NewMeter(PrometheusCompat(true)) m := NewMeter()
m.Histogram("foo", "key", "val").Update(15) if err := m.Init(); err != nil {
t.Fatal(err)
}
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) buf := bytes.NewBuffer(nil)
_ = m.Write(buf, meter.WriteProcessMetrics(false), meter.WriteFDMetrics(false))
_ = m.Write(buf) if !bytes.Contains(buf.Bytes(), []byte(`micro_client_request_total{endpoint="Service.Method",status="failure",code="500"} 1`)) {
t.Logf("\n%s", buf.Bytes()) t.Fatalf("invalid metrics output: %s", buf.Bytes())
}
} }