Bladeren bron

feat: mysql and redis metric support (#2355)

* feat: mysql and redis metric support

* feat: mysql and redis metric support

* feat: mysql and redis metric support

Co-authored-by: dawn.zhou <dawn.zhou@yijinin.com>
dawn_zhou 2 jaren geleden
bovenliggende
commit
ae7f1aabdd

+ 11 - 1
core/metric/counter.go

@@ -1,8 +1,10 @@
 package metric
 package metric
 
 
 import (
 import (
-	prom "github.com/prometheus/client_golang/prometheus"
 	"github.com/zeromicro/go-zero/core/proc"
 	"github.com/zeromicro/go-zero/core/proc"
+	"github.com/zeromicro/go-zero/core/prometheus"
+
+	prom "github.com/prometheus/client_golang/prometheus"
 )
 )
 
 
 type (
 type (
@@ -47,10 +49,18 @@ func NewCounterVec(cfg *CounterVecOpts) CounterVec {
 }
 }
 
 
 func (cv *promCounterVec) Inc(labels ...string) {
 func (cv *promCounterVec) Inc(labels ...string) {
+	if !prometheus.Enabled() {
+		return
+	}
+
 	cv.counter.WithLabelValues(labels...).Inc()
 	cv.counter.WithLabelValues(labels...).Inc()
 }
 }
 
 
 func (cv *promCounterVec) Add(v float64, labels ...string) {
 func (cv *promCounterVec) Add(v float64, labels ...string) {
+	if !prometheus.Enabled() {
+		return
+	}
+
 	cv.counter.WithLabelValues(labels...).Add(v)
 	cv.counter.WithLabelValues(labels...).Add(v)
 }
 }
 
 

+ 12 - 0
core/metric/counter_test.go

@@ -3,6 +3,8 @@ package metric
 import (
 import (
 	"testing"
 	"testing"
 
 
+	"github.com/zeromicro/go-zero/core/prometheus"
+
 	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 )
 )
@@ -21,6 +23,7 @@ func TestNewCounterVec(t *testing.T) {
 }
 }
 
 
 func TestCounterIncr(t *testing.T) {
 func TestCounterIncr(t *testing.T) {
+	startAgent()
 	counterVec := NewCounterVec(&CounterVecOpts{
 	counterVec := NewCounterVec(&CounterVecOpts{
 		Namespace: "http_client",
 		Namespace: "http_client",
 		Subsystem: "call",
 		Subsystem: "call",
@@ -37,6 +40,7 @@ func TestCounterIncr(t *testing.T) {
 }
 }
 
 
 func TestCounterAdd(t *testing.T) {
 func TestCounterAdd(t *testing.T) {
+	startAgent()
 	counterVec := NewCounterVec(&CounterVecOpts{
 	counterVec := NewCounterVec(&CounterVecOpts{
 		Namespace: "rpc_server",
 		Namespace: "rpc_server",
 		Subsystem: "requests",
 		Subsystem: "requests",
@@ -51,3 +55,11 @@ func TestCounterAdd(t *testing.T) {
 	r := testutil.ToFloat64(cv.counter)
 	r := testutil.ToFloat64(cv.counter)
 	assert.Equal(t, float64(33), r)
 	assert.Equal(t, float64(33), r)
 }
 }
+
+func startAgent() {
+	prometheus.StartAgent(prometheus.Config{
+		Host: "127.0.0.1",
+		Port: 9101,
+		Path: "/metrics",
+	})
+}

+ 15 - 1
core/metric/gauge.go

@@ -1,8 +1,10 @@
 package metric
 package metric
 
 
 import (
 import (
-	prom "github.com/prometheus/client_golang/prometheus"
 	"github.com/zeromicro/go-zero/core/proc"
 	"github.com/zeromicro/go-zero/core/proc"
+	"github.com/zeromicro/go-zero/core/prometheus"
+
+	prom "github.com/prometheus/client_golang/prometheus"
 )
 )
 
 
 type (
 type (
@@ -50,14 +52,26 @@ func NewGaugeVec(cfg *GaugeVecOpts) GaugeVec {
 }
 }
 
 
 func (gv *promGaugeVec) Inc(labels ...string) {
 func (gv *promGaugeVec) Inc(labels ...string) {
+	if !prometheus.Enabled() {
+		return
+	}
+
 	gv.gauge.WithLabelValues(labels...).Inc()
 	gv.gauge.WithLabelValues(labels...).Inc()
 }
 }
 
 
 func (gv *promGaugeVec) Add(v float64, labels ...string) {
 func (gv *promGaugeVec) Add(v float64, labels ...string) {
+	if !prometheus.Enabled() {
+		return
+	}
+
 	gv.gauge.WithLabelValues(labels...).Add(v)
 	gv.gauge.WithLabelValues(labels...).Add(v)
 }
 }
 
 
 func (gv *promGaugeVec) Set(v float64, labels ...string) {
 func (gv *promGaugeVec) Set(v float64, labels ...string) {
+	if !prometheus.Enabled() {
+		return
+	}
+
 	gv.gauge.WithLabelValues(labels...).Set(v)
 	gv.gauge.WithLabelValues(labels...).Set(v)
 }
 }
 
 

+ 3 - 0
core/metric/gauge_test.go

@@ -21,6 +21,7 @@ func TestNewGaugeVec(t *testing.T) {
 }
 }
 
 
 func TestGaugeInc(t *testing.T) {
 func TestGaugeInc(t *testing.T) {
+	startAgent()
 	gaugeVec := NewGaugeVec(&GaugeVecOpts{
 	gaugeVec := NewGaugeVec(&GaugeVecOpts{
 		Namespace: "rpc_client2",
 		Namespace: "rpc_client2",
 		Subsystem: "requests",
 		Subsystem: "requests",
@@ -37,6 +38,7 @@ func TestGaugeInc(t *testing.T) {
 }
 }
 
 
 func TestGaugeAdd(t *testing.T) {
 func TestGaugeAdd(t *testing.T) {
+	startAgent()
 	gaugeVec := NewGaugeVec(&GaugeVecOpts{
 	gaugeVec := NewGaugeVec(&GaugeVecOpts{
 		Namespace: "rpc_client",
 		Namespace: "rpc_client",
 		Subsystem: "request",
 		Subsystem: "request",
@@ -53,6 +55,7 @@ func TestGaugeAdd(t *testing.T) {
 }
 }
 
 
 func TestGaugeSet(t *testing.T) {
 func TestGaugeSet(t *testing.T) {
+	startAgent()
 	gaugeVec := NewGaugeVec(&GaugeVecOpts{
 	gaugeVec := NewGaugeVec(&GaugeVecOpts{
 		Namespace: "http_client",
 		Namespace: "http_client",
 		Subsystem: "request",
 		Subsystem: "request",

+ 7 - 1
core/metric/histogram.go

@@ -1,8 +1,10 @@
 package metric
 package metric
 
 
 import (
 import (
-	prom "github.com/prometheus/client_golang/prometheus"
 	"github.com/zeromicro/go-zero/core/proc"
 	"github.com/zeromicro/go-zero/core/proc"
+	"github.com/zeromicro/go-zero/core/prometheus"
+
+	prom "github.com/prometheus/client_golang/prometheus"
 )
 )
 
 
 type (
 type (
@@ -53,6 +55,10 @@ func NewHistogramVec(cfg *HistogramVecOpts) HistogramVec {
 }
 }
 
 
 func (hv *promHistogramVec) Observe(v int64, labels ...string) {
 func (hv *promHistogramVec) Observe(v int64, labels ...string) {
+	if !prometheus.Enabled() {
+		return
+	}
+
 	hv.histogram.WithLabelValues(labels...).Observe(float64(v))
 	hv.histogram.WithLabelValues(labels...).Observe(float64(v))
 }
 }
 
 

+ 1 - 0
core/metric/histogram_test.go

@@ -21,6 +21,7 @@ func TestNewHistogramVec(t *testing.T) {
 }
 }
 
 
 func TestHistogramObserve(t *testing.T) {
 func TestHistogramObserve(t *testing.T) {
+	startAgent()
 	histogramVec := NewHistogramVec(&HistogramVecOpts{
 	histogramVec := NewHistogramVec(&HistogramVecOpts{
 		Name:    "counts",
 		Name:    "counts",
 		Help:    "rpc server requests duration(ms).",
 		Help:    "rpc server requests duration(ms).",

+ 39 - 0
core/stores/redis/hook.go

@@ -56,6 +56,11 @@ func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
 		logDuration(ctx, cmd, duration)
 		logDuration(ctx, cmd, duration)
 	}
 	}
 
 
+	metricReqDur.Observe(int64(duration/time.Millisecond), cmd.Name())
+	if msg := errFormat(err); len(msg) > 0 {
+		metricReqErr.Inc(cmd.Name(), msg)
+	}
+
 	return nil
 	return nil
 }
 }
 
 
@@ -98,9 +103,43 @@ func (h hook) AfterProcessPipeline(ctx context.Context, cmds []red.Cmder) error
 		logDuration(ctx, cmds[0], duration)
 		logDuration(ctx, cmds[0], duration)
 	}
 	}
 
 
+	metricReqDur.Observe(int64(duration/time.Millisecond), "Pipeline")
+	if msg := errFormat(batchError.Err()); len(msg) > 0 {
+		metricReqErr.Inc("Pipeline", msg)
+	}
+
 	return nil
 	return nil
 }
 }
 
 
+func errFormat(err error) string {
+	if err == nil || err == red.Nil {
+		return ""
+	}
+
+	es := err.Error()
+	switch {
+	case strings.HasPrefix(es, "read"):
+		return "read timeout"
+	case strings.HasPrefix(es, "dial"):
+		if strings.Contains(es, "connection refused") {
+			return "connection refused"
+		}
+		return "dial timeout"
+	case strings.HasPrefix(es, "write"):
+		return "write timeout"
+	case strings.Contains(es, "EOF"):
+		return "eof"
+	case strings.Contains(es, "reset"):
+		return "reset"
+	case strings.Contains(es, "broken"):
+		return "broken pipe"
+	case strings.Contains(es, "breaker"):
+		return "breaker"
+	default:
+		return "unexpected error"
+	}
+}
+
 func logDuration(ctx context.Context, cmd red.Cmder, duration time.Duration) {
 func logDuration(ctx context.Context, cmd red.Cmder, duration time.Duration) {
 	var buf strings.Builder
 	var buf strings.Builder
 	for i, arg := range cmd.Args() {
 	for i, arg := range cmd.Args() {

+ 23 - 0
core/stores/redis/metrics.go

@@ -0,0 +1,23 @@
+package redis
+
+import "github.com/zeromicro/go-zero/core/metric"
+
+const namespace = "redis_client"
+
+var (
+	metricReqDur = metric.NewHistogramVec(&metric.HistogramVecOpts{
+		Namespace: namespace,
+		Subsystem: "requests",
+		Name:      "duration_ms",
+		Help:      "redis client requests duration(ms).",
+		Labels:    []string{"command"},
+		Buckets:   []float64{5, 10, 25, 50, 100, 250, 500, 1000, 2500},
+	})
+	metricReqErr = metric.NewCounterVec(&metric.CounterVecOpts{
+		Namespace: namespace,
+		Subsystem: "requests",
+		Name:      "error_total",
+		Help:      "redis client requests error count.",
+		Labels:    []string{"command", "error"},
+	})
+)

+ 23 - 0
core/stores/sqlx/metrics.go

@@ -0,0 +1,23 @@
+package sqlx
+
+import "github.com/zeromicro/go-zero/core/metric"
+
+const namespace = "mysql_client"
+
+var (
+	metricReqDur = metric.NewHistogramVec(&metric.HistogramVecOpts{
+		Namespace: namespace,
+		Subsystem: "requests",
+		Name:      "durations_ms",
+		Help:      "mysql client requests duration(ms).",
+		Labels:    []string{"command"},
+		Buckets:   []float64{5, 10, 25, 50, 100, 250, 500, 1000, 2500},
+	})
+	metricReqErr = metric.NewCounterVec(&metric.CounterVecOpts{
+		Namespace: namespace,
+		Subsystem: "requests",
+		Name:      "error_total",
+		Help:      "mysql client requests error count.",
+		Labels:    []string{"command", "error"},
+	})
+)

+ 22 - 2
core/stores/sqlx/sqlconn.go

@@ -154,6 +154,10 @@ func (db *commonSqlConn) ExecCtx(ctx context.Context, q string, args ...interfac
 		return err
 		return err
 	}, db.acceptable)
 	}, db.acceptable)
 
 
+	if err == breaker.ErrServiceUnavailable {
+		metricReqErr.Inc("Exec", "breaker")
+	}
+
 	return
 	return
 }
 }
 
 
@@ -187,6 +191,10 @@ func (db *commonSqlConn) PrepareCtx(ctx context.Context, query string) (stmt Stm
 		return nil
 		return nil
 	}, db.acceptable)
 	}, db.acceptable)
 
 
+	if err == breaker.ErrServiceUnavailable {
+		metricReqErr.Inc("Prepare", "breaker")
+	}
+
 	return
 	return
 }
 }
 
 
@@ -270,9 +278,15 @@ func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Contex
 		endSpan(span, err)
 		endSpan(span, err)
 	}()
 	}()
 
 
-	return db.brk.DoWithAcceptable(func() error {
+	err = db.brk.DoWithAcceptable(func() error {
 		return transact(ctx, db, db.beginTx, fn)
 		return transact(ctx, db, db.beginTx, fn)
 	}, db.acceptable)
 	}, db.acceptable)
+
+	if err == breaker.ErrServiceUnavailable {
+		metricReqErr.Inc("Transact", "breaker")
+	}
+
+	return
 }
 }
 
 
 func (db *commonSqlConn) acceptable(err error) bool {
 func (db *commonSqlConn) acceptable(err error) bool {
@@ -287,7 +301,7 @@ func (db *commonSqlConn) acceptable(err error) bool {
 func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error,
 func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error,
 	q string, args ...interface{}) (err error) {
 	q string, args ...interface{}) (err error) {
 	var qerr error
 	var qerr error
-	return db.brk.DoWithAcceptable(func() error {
+	err = db.brk.DoWithAcceptable(func() error {
 		conn, err := db.connProv()
 		conn, err := db.connProv()
 		if err != nil {
 		if err != nil {
 			db.onError(err)
 			db.onError(err)
@@ -301,6 +315,12 @@ func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows)
 	}, func(err error) bool {
 	}, func(err error) bool {
 		return qerr == err || db.acceptable(err)
 		return qerr == err || db.acceptable(err)
 	})
 	})
+
+	if err == breaker.ErrServiceUnavailable {
+		metricReqErr.Inc("queryRows", "breaker")
+	}
+
+	return
 }
 }
 
 
 func (s statement) Close() error {
 func (s statement) Close() error {

+ 4 - 3
core/stores/sqlx/sqlconn_test.go

@@ -17,7 +17,8 @@ func init() {
 }
 }
 
 
 func TestSqlConn(t *testing.T) {
 func TestSqlConn(t *testing.T) {
-	mock := buildConn()
+	mock, err := buildConn()
+	assert.Nil(t, err)
 	mock.ExpectExec("any")
 	mock.ExpectExec("any")
 	mock.ExpectQuery("any").WillReturnRows(sqlmock.NewRows([]string{"foo"}))
 	mock.ExpectQuery("any").WillReturnRows(sqlmock.NewRows([]string{"foo"}))
 	conn := NewMysql(mockedDatasource)
 	conn := NewMysql(mockedDatasource)
@@ -50,8 +51,8 @@ func TestSqlConn(t *testing.T) {
 	}))
 	}))
 }
 }
 
 
-func buildConn() (mock sqlmock.Sqlmock) {
-	connManager.GetResource(mockedDatasource, func() (io.Closer, error) {
+func buildConn() (mock sqlmock.Sqlmock, err error) {
+	_, err = connManager.GetResource(mockedDatasource, func() (io.Closer, error) {
 		var db *sql.DB
 		var db *sql.DB
 		var err error
 		var err error
 		db, mock, err = sqlmock.New()
 		db, mock, err = sqlmock.New()

+ 2 - 0
core/stores/sqlx/stmt.go

@@ -135,6 +135,8 @@ func (e *realSqlGuard) finish(ctx context.Context, err error) {
 	if err != nil {
 	if err != nil {
 		logSqlError(ctx, e.stmt, err)
 		logSqlError(ctx, e.stmt, err)
 	}
 	}
+
+	metricReqDur.Observe(int64(duration/time.Millisecond), e.command)
 }
 }
 
 
 func (e *realSqlGuard) start(q string, args ...interface{}) error {
 func (e *realSqlGuard) start(q string, args ...interface{}) error {

+ 0 - 5
rest/handler/prometheushandler.go

@@ -6,7 +6,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/zeromicro/go-zero/core/metric"
 	"github.com/zeromicro/go-zero/core/metric"
-	"github.com/zeromicro/go-zero/core/prometheus"
 	"github.com/zeromicro/go-zero/core/timex"
 	"github.com/zeromicro/go-zero/core/timex"
 	"github.com/zeromicro/go-zero/rest/internal/response"
 	"github.com/zeromicro/go-zero/rest/internal/response"
 )
 )
@@ -35,10 +34,6 @@ var (
 // PrometheusHandler returns a middleware that reports stats to prometheus.
 // PrometheusHandler returns a middleware that reports stats to prometheus.
 func PrometheusHandler(path string) func(http.Handler) http.Handler {
 func PrometheusHandler(path string) func(http.Handler) http.Handler {
 	return func(next http.Handler) http.Handler {
 	return func(next http.Handler) http.Handler {
-		if !prometheus.Enabled() {
-			return next
-		}
-
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			startTime := timex.Now()
 			startTime := timex.Now()
 			cw := &response.WithCodeResponseWriter{Writer: w}
 			cw := &response.WithCodeResponseWriter{Writer: w}

+ 1 - 5
zrpc/internal/clientinterceptors/prometheusinterceptor.go

@@ -6,8 +6,8 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/zeromicro/go-zero/core/metric"
 	"github.com/zeromicro/go-zero/core/metric"
-	"github.com/zeromicro/go-zero/core/prometheus"
 	"github.com/zeromicro/go-zero/core/timex"
 	"github.com/zeromicro/go-zero/core/timex"
+
 	"google.golang.org/grpc"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/status"
 	"google.golang.org/grpc/status"
 )
 )
@@ -36,10 +36,6 @@ var (
 // PrometheusInterceptor is an interceptor that reports to prometheus server.
 // PrometheusInterceptor is an interceptor that reports to prometheus server.
 func PrometheusInterceptor(ctx context.Context, method string, req, reply interface{},
 func PrometheusInterceptor(ctx context.Context, method string, req, reply interface{},
 	cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
 	cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
-	if !prometheus.Enabled() {
-		return invoker(ctx, method, req, reply, cc, opts...)
-	}
-
 	startTime := timex.Now()
 	startTime := timex.Now()
 	err := invoker(ctx, method, req, reply, cc, opts...)
 	err := invoker(ctx, method, req, reply, cc, opts...)
 	metricClientReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), method)
 	metricClientReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), method)

+ 1 - 5
zrpc/internal/serverinterceptors/prometheusinterceptor.go

@@ -6,8 +6,8 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/zeromicro/go-zero/core/metric"
 	"github.com/zeromicro/go-zero/core/metric"
-	"github.com/zeromicro/go-zero/core/prometheus"
 	"github.com/zeromicro/go-zero/core/timex"
 	"github.com/zeromicro/go-zero/core/timex"
+
 	"google.golang.org/grpc"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/status"
 	"google.golang.org/grpc/status"
 )
 )
@@ -36,10 +36,6 @@ var (
 // UnaryPrometheusInterceptor reports the statistics to the prometheus server.
 // UnaryPrometheusInterceptor reports the statistics to the prometheus server.
 func UnaryPrometheusInterceptor(ctx context.Context, req interface{},
 func UnaryPrometheusInterceptor(ctx context.Context, req interface{},
 	info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
 	info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
-	if !prometheus.Enabled() {
-		return handler(ctx, req)
-	}
-
 	startTime := timex.Now()
 	startTime := timex.Now()
 	resp, err := handler(ctx, req)
 	resp, err := handler(ctx, req)
 	metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), info.FullMethod)
 	metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), info.FullMethod)