diff --git a/go.mod b/go.mod index 0fede20..c98c3bf 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,21 @@ module github.com/openGemini/opengemini-client-go go 1.19 -require github.com/stretchr/testify v1.8.4 +require ( + github.com/prometheus/client_golang v1.19.1 + github.com/stretchr/testify v1.9.0 +) 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/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + golang.org/x/sys v0.17.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fa4b6e6..dff6d14 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,32 @@ +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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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/opengemini/client_impl.go b/opengemini/client_impl.go index f54f5e2..70b651b 100644 --- a/opengemini/client_impl.go +++ b/opengemini/client_impl.go @@ -22,6 +22,7 @@ type client struct { cli *http.Client prevIdx atomic.Int32 dataChan sync.Map + metrics *metrics cancel context.CancelFunc } @@ -62,6 +63,7 @@ func newClient(c *Config) (Client, error) { config: c, endpoints: buildEndpoints(c.Addresses, c.TlsEnabled), cli: newHttpClient(*c), + metrics: newMetricsProvider(), cancel: cancel, } client.prevIdx.Store(-1) diff --git a/opengemini/metrics.go b/opengemini/metrics.go new file mode 100644 index 0000000..cd93b49 --- /dev/null +++ b/opengemini/metrics.go @@ -0,0 +1,118 @@ +package opengemini + +import "github.com/prometheus/client_golang/prometheus" + +const ( + MetricsNamespace = "opengemini" + MetricsSubsystem = "client" +) + +var _ prometheus.Collector = (*metrics)(nil) + +// metrics custom indicators, implementing the prometheus.Collector interface +type metrics struct { + // queryCounter count all queries + queryCounter prometheus.Counter + // writeCounter count all write requests + writeCounter prometheus.Counter + // queryLatency calculate the average of the queries, unit milliseconds + queryLatency prometheus.Summary + // writeLatency calculate the average of the writes, unit milliseconds + writeLatency prometheus.Summary + // queryDatabaseCounter Count queries and classify using measurement + queryDatabaseCounter *prometheus.CounterVec + // writeDatabaseCounter count write requests and classify using measurement + writeDatabaseCounter *prometheus.CounterVec + // queryDatabaseLatency calculate the average of the queries for database, unit milliseconds + queryDatabaseLatency *prometheus.SummaryVec + // writeDatabaseLatency calculate the average of the writes for database, unit milliseconds + writeDatabaseLatency *prometheus.SummaryVec +} + +func (m *metrics) Describe(chan<- *prometheus.Desc) {} + +func (m *metrics) Collect(ch chan<- prometheus.Metric) { + ch <- m.queryCounter + ch <- m.writeCounter + ch <- m.queryLatency + ch <- m.writeLatency + m.queryDatabaseCounter.Collect(ch) + m.writeDatabaseCounter.Collect(ch) + m.queryDatabaseLatency.Collect(ch) + m.writeDatabaseLatency.Collect(ch) +} + +// newMetricsProvider returns metrics registered to registerer. +func newMetricsProvider() *metrics { + constLabels := map[string]string{ + "client": "go", // distinguish from other language client + } + constQuantiles := map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001} + labelNames := []string{"database"} + + m := &metrics{ + queryCounter: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Subsystem: MetricsSubsystem, + Name: "query_total", + Help: "Count of opengemini queries", + ConstLabels: constLabels, + }), + writeCounter: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Subsystem: MetricsSubsystem, + Name: "write_total", + Help: "Count of opengemini writes", + ConstLabels: constLabels, + }), + queryLatency: prometheus.NewSummary(prometheus.SummaryOpts{ + Namespace: MetricsNamespace, + Subsystem: MetricsSubsystem, + Name: "query_latency", + Help: "Calculate the average of the queries", + ConstLabels: constLabels, + Objectives: constQuantiles, + }), + writeLatency: prometheus.NewSummary(prometheus.SummaryOpts{ + Namespace: MetricsNamespace, + Subsystem: MetricsSubsystem, + Name: "write_latency", + Help: "Calculate the average of the writes", + ConstLabels: constLabels, + Objectives: constQuantiles, + }), + queryDatabaseCounter: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Subsystem: MetricsSubsystem, + Name: "query_database_total", + Help: "Count of opengemini queries and classify using measurement", + ConstLabels: constLabels, + }, labelNames), + writeDatabaseCounter: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: MetricsNamespace, + Subsystem: MetricsSubsystem, + Name: "write_database_total", + Help: "Count of opengemini writes and classify using measurement", + ConstLabels: constLabels, + }, labelNames), + queryDatabaseLatency: prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Namespace: MetricsNamespace, + Subsystem: MetricsSubsystem, + Name: "query_database_latency", + Help: "Calculate the average of the queries for database", + ConstLabels: constLabels, + Objectives: constQuantiles, + }, labelNames), + writeDatabaseLatency: prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Namespace: MetricsNamespace, + Subsystem: MetricsSubsystem, + Name: "write_database_latency", + Help: "Calculate the average of the writes for database", + ConstLabels: constLabels, + Objectives: constQuantiles, + }, labelNames), + } + + prometheus.MustRegister(m) + return m +} diff --git a/opengemini/query.go b/opengemini/query.go index 1a97a41..b2093c1 100644 --- a/opengemini/query.go +++ b/opengemini/query.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "time" ) type Query struct { @@ -30,7 +31,17 @@ func (c *client) Query(q Query) (*QueryResult, error) { req.queryValues.Add("rp", q.RetentionPolicy) req.queryValues.Add("epoch", q.Precision.Epoch()) + // metric + c.metrics.queryCounter.Add(1) + c.metrics.queryDatabaseCounter.WithLabelValues(q.Database).Add(1) + startAt := time.Now() + resp, err := c.executeHttpGet(UrlQuery, req) + + cost := float64(time.Since(startAt).Milliseconds()) + c.metrics.queryLatency.Observe(cost) + c.metrics.queryDatabaseLatency.WithLabelValues(q.Database).Observe(cost) + if err != nil { return nil, errors.New("query request failed, error: " + err.Error()) } diff --git a/opengemini/write.go b/opengemini/write.go index 3bd1e60..67495ef 100644 --- a/opengemini/write.go +++ b/opengemini/write.go @@ -154,5 +154,16 @@ func (c *client) innerWrite(database string, buffer *bytes.Buffer) (*http.Respon req.header.Set("Accept-Encoding", "gzip") } req.queryValues.Add("db", database) - return c.executeHttpPost(UrlWrite, req) + + c.metrics.writeCounter.Add(1) + c.metrics.writeDatabaseCounter.WithLabelValues(database).Add(1) + startAt := time.Now() + + response, err := c.executeHttpPost(UrlWrite, req) + + cost := float64(time.Since(startAt).Milliseconds()) + c.metrics.writeLatency.Observe(cost) + c.metrics.writeDatabaseLatency.WithLabelValues(database).Observe(cost) + + return response, err }