From 602c94be2a37db9721d3dc3dfb384f088d09cbfc Mon Sep 17 00:00:00 2001 From: Ben Keith Date: Mon, 4 Oct 2021 13:58:20 -0400 Subject: [PATCH] Add gRPC interceptor to request tracker (#224) This makes those metrics reusable between HTTP and gRPC servers --- go.mod | 1 + go.sum | 4 ++++ web/reqcounter.go | 29 +++++++++++++++++++++++++++-- web/reqcounter_test.go | 11 +++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 84969ce..caf4810 100644 --- a/go.mod +++ b/go.mod @@ -26,5 +26,6 @@ require ( github.com/smartystreets/goconvey v1.6.4 github.com/stretchr/testify v1.7.0 github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec + google.golang.org/grpc v1.40.0 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect ) diff --git a/go.sum b/go.sum index fd8fc3c..13ef3a1 100644 --- a/go.sum +++ b/go.sum @@ -1267,6 +1267,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210427231257-85d9c07bbe3a/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1396,6 +1397,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1595,6 +1597,7 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210312152112-fc591d9ea70f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -1623,6 +1626,7 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/web/reqcounter.go b/web/reqcounter.go index 5320990..5d05e22 100644 --- a/web/reqcounter.go +++ b/web/reqcounter.go @@ -1,12 +1,14 @@ package web import ( + "context" "net/http" "sync/atomic" "time" "github.com/signalfx/golib/v3/datapoint" "github.com/signalfx/golib/v3/sfxclient" + "google.golang.org/grpc" ) // RequestCounter is a negroni handler that tracks connection stats @@ -29,16 +31,39 @@ func (m *RequestCounter) Wrap(next http.Handler) http.Handler { return http.HandlerFunc(f) } -func (m *RequestCounter) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.Handler) { +// wrapRequest will wrap the request handler func in a generic way that times the request and +// maintains counts for org metrics. +func (m *RequestCounter) wrapRequest(handler func()) { atomic.AddInt64(&m.TotalConnections, 1) atomic.AddInt64(&m.ActiveConnections, 1) defer atomic.AddInt64(&m.ActiveConnections, -1) start := time.Now() - next.ServeHTTP(rw, r) + + handler() + reqDuration := time.Since(start) atomic.AddInt64(&m.TotalProcessingTimeNs, reqDuration.Nanoseconds()) } +// ServeHTTP makes an HTTP handler that tracks the requests. +func (m *RequestCounter) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.Handler) { + m.wrapRequest(func() { next.ServeHTTP(rw, r) }) +} + +// GRPCInterceptor makes a unary GRPC interceptor to track requests. +func (m *RequestCounter) GRPCInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + var resp interface{} + var err error + + m.wrapRequest(func() { + // This is safe because WrapRequest should always call this func synchronously and never in + // a separate goroutine. + resp, err = handler(ctx, req) + }) + + return resp, err +} + // Datapoints returns stats on total connections, active connections, and total processing time func (m *RequestCounter) Datapoints() []*datapoint.Datapoint { return []*datapoint.Datapoint{ diff --git a/web/reqcounter_test.go b/web/reqcounter_test.go index 03c55c0..4030fd6 100644 --- a/web/reqcounter_test.go +++ b/web/reqcounter_test.go @@ -1,6 +1,7 @@ package web import ( + "context" "net/http" "testing" "time" @@ -26,4 +27,14 @@ func TestRequestCounter(t *testing.T) { m.ServeHTTP(nil, nil, f) assert.Equal(t, 3, len(m.Datapoints())) + + resp, err := m.GRPCInterceptor(context.Background(), nil, nil, func(ctx context.Context, req interface{}) (interface{}, error) { + assert.EqualValues(t, 1, m.ActiveConnections) + assert.EqualValues(t, 3, m.TotalConnections) + assert.NotEqual(t, 0, m.TotalProcessingTimeNs) + + return 5, nil + }) + assert.NoError(t, err) + assert.Equal(t, 5, resp) }