diff --git a/go.mod b/go.mod index dda720d3fd8bcb0198881de20a4568223d7c7a0a..1cd8f4cff1e092295b94709ced40805413aa6e6a 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/getsentry/raven-go v0.2.0 github.com/getsentry/sentry-go v0.13.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 github.com/lightstep/lightstep-tracer-go v0.25.0 github.com/oklog/ulid/v2 v2.0.2 @@ -19,17 +20,19 @@ require ( github.com/stretchr/testify v1.10.0 github.com/uber/jaeger-client-go v2.29.1+incompatible gitlab.com/gitlab-org/go/reopen v1.0.0 - go.opencensus.io v0.23.0 + go.opencensus.io v0.24.0 golang.org/x/crypto v0.41.0 - google.golang.org/api v0.54.0 + google.golang.org/api v0.162.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.36.6 gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 ) require ( - cloud.google.com/go v0.92.2 // indirect - cloud.google.com/go/trace v0.1.0 // indirect + cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect + cloud.google.com/go/monitoring v1.18.0 // indirect + cloud.google.com/go/trace v1.10.5 // indirect github.com/DataDog/datadog-go v4.4.0+incompatible // indirect github.com/DataDog/sketches-go v1.0.0 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.1 // indirect @@ -41,13 +44,18 @@ require ( github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect + github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -65,15 +73,21 @@ require ( github.com/tklauser/go-sysconf v0.3.4 // indirect github.com/tklauser/numcpus v0.2.1 // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect go.uber.org/atomic v1.4.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect + golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7653da72ae084751ff43791c522d6a3cc5561135..be5f4ec5bda0b44c4a9613818a42ac40dff617b7 100644 --- a/go.sum +++ b/go.sum @@ -23,16 +23,23 @@ cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAV cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.92.2 h1:podK44+0gcW5rWGMjJiPH0+rzkCTQx/zT0qF5CLqVkM= cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/monitoring v1.18.0 h1:NfkDLQDG2UR3WYZVQE8kwSbUIEyIqJUPl+aOQdFH1T4= +cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg= cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk= cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -43,10 +50,11 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/trace v0.1.0 h1:nUGUK79FOkN0UGUXhBmVBkbu1PYsHe0YyFSPLOD9Npg= -cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g= +cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/trace v1.10.5 h1:0pr4lIKJ5XZFYD9GtxXEWr0KkVeigc3wlGpZco0X1oA= +cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M= contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4= contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -85,6 +93,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= +github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -97,6 +107,10 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= @@ -111,6 +125,11 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -120,8 +139,9 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -148,8 +168,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -192,14 +213,21 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210804190019-f964ff605595 h1:uNrRgpnKjTfxu4qHaZAAs3eKTYV1EzGF3dAykpnxgDE= github.com/google/pprof v0.0.0-20210804190019-f964ff605595/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -284,6 +312,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -292,6 +322,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= @@ -318,8 +351,21 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -507,8 +553,8 @@ golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -600,15 +646,15 @@ google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59t google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= +google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -664,8 +710,12 @@ google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc h1:Nf+EdcTLHR8qDNN/KfkQL0u0ssxt9OhbaWCl5C0ucEI= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw= +google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/metrics/grpc/factory.go b/metrics/grpc/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..9400465edbadcd8b4e107270deb0fad220b700b5 --- /dev/null +++ b/metrics/grpc/factory.go @@ -0,0 +1,68 @@ +package metricsgrpc + +import ( + prom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" + "github.com/prometheus/client_golang/prometheus" +) + +// ServerMetricsFactory creates gRPC server metric interceptors. Created by NewServerMetricsFactory. +type ServerMetricsFactory func() (*ServerMetrics, error) + +// NewServerMetricsFactory creates a new factory for gRPC server metrics. +func NewServerMetricsFactory(opts ...FactoryOption) (ServerMetricsFactory, error) { + factoryConfig := applyFactoryOptions(opts) + + return func() (*ServerMetrics, error) { + metrics := prom.NewServerMetrics( + prom.WithServerCounterOptions(prom.WithNamespace(factoryConfig.namespace)), + prom.WithServerHandlingTimeHistogram( + prom.WithHistogramBuckets(factoryConfig.reqDurationBuckets), + prom.WithHistogramNamespace(factoryConfig.namespace), + ), + ) + + if err := prometheus.Register(metrics); err != nil { + if _, ok := err.(prometheus.AlreadyRegisteredError); !ok { + return nil, err + } + } + + reqSizeHistogram := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "request_size_bytes", + Help: "RPC request size in bytes.", + Buckets: prometheus.ExponentialBuckets(100, 10, 8), + Subsystem: factoryConfig.subsystem, + Namespace: factoryConfig.namespace, + }, + []string{"grpc_service", "grpc_method", "grpc_type"}, + ) + respSizeHistogram := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "response_size_bytes", + Help: "RPC response size in bytes.", + Buckets: prometheus.ExponentialBuckets(100, 10, 8), + Subsystem: factoryConfig.subsystem, + Namespace: factoryConfig.namespace, + }, + []string{"grpc_service", "grpc_method", "grpc_type"}, + ) + + if err := prometheus.Register(reqSizeHistogram); err != nil { + if _, ok := err.(prometheus.AlreadyRegisteredError); !ok { + return nil, err + } + } + if err := prometheus.Register(respSizeHistogram); err != nil { + if _, ok := err.(prometheus.AlreadyRegisteredError); !ok { + return nil, err + } + } + + return &ServerMetrics{ + ServerMetrics: metrics, + reqSizeHistogram: reqSizeHistogram, + respSizeHistogram: respSizeHistogram, + }, nil + }, nil +} diff --git a/metrics/grpc/factory_options.go b/metrics/grpc/factory_options.go new file mode 100644 index 0000000000000000000000000000000000000000..41fdaf01fd3b69d41112c85718963d7848387739 --- /dev/null +++ b/metrics/grpc/factory_options.go @@ -0,0 +1,70 @@ +package metricsgrpc + +import "github.com/prometheus/client_golang/prometheus" + +// FactoryOption configures the gRPC server metrics factory. +type FactoryOption func(*factoryConfig) + +type factoryConfig struct { + namespace string + subsystem string + reqSizeBuckets []float64 + respSizeBuckets []float64 + reqDurationBuckets []float64 +} + +const defaultNamespace = "gitlab" +const defaultSubsystem = "grpc" + +func defaultFactoryConfig() *factoryConfig { + return &factoryConfig{ + namespace: defaultNamespace, + subsystem: defaultSubsystem, + reqSizeBuckets: prometheus.DefBuckets, + respSizeBuckets: prometheus.DefBuckets, + reqDurationBuckets: prometheus.DefBuckets, + } +} + +func applyFactoryOptions(opts []FactoryOption) *factoryConfig { + config := defaultFactoryConfig() + for _, opt := range opts { + opt(config) + } + return config +} + +// WithNamespace sets the Prometheus namespace for the metrics. +func WithNamespace(namespace string) FactoryOption { + return func(c *factoryConfig) { + c.namespace = namespace + } +} + +// WithSubsystem sets the Prometheus subsystem for the metrics. +func WithSubsystem(subsystem string) FactoryOption { + return func(c *factoryConfig) { + c.subsystem = subsystem + } +} + +// WithReqSizeBuckets sets the buckets for the request size histogram. +func WithReqSizeBuckets(buckets []float64) FactoryOption { + return func(c *factoryConfig) { + c.reqSizeBuckets = buckets + } +} + +// WithRespSizeBuckets sets the buckets for the response size histogram. +func WithRespSizeBuckets(buckets []float64) FactoryOption { + return func(c *factoryConfig) { + c.respSizeBuckets = buckets + } +} + +// WithReqDurationBuckets sets the buckets for the request duration histogram. +func WithReqDurationBuckets(buckets []float64) FactoryOption { + return func(c *factoryConfig) { + c.reqDurationBuckets = buckets + } +} diff --git a/metrics/grpc/factory_options_test.go b/metrics/grpc/factory_options_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c210d03052b4d51873a31b541ead784e37ac9ed8 --- /dev/null +++ b/metrics/grpc/factory_options_test.go @@ -0,0 +1,111 @@ +package metricsgrpc + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" +) + +func TestFactoryOptions(t *testing.T) { + t.Parallel() + + testBuckets := []float64{1, 2, 3} + + testCases := []struct { + desc string + opts []FactoryOption + expected *factoryConfig + }{ + { + desc: "with no options", + opts: nil, + expected: &factoryConfig{ + reqSizeBuckets: prometheus.DefBuckets, + respSizeBuckets: prometheus.DefBuckets, + reqDurationBuckets: prometheus.DefBuckets, + namespace: defaultNamespace, + subsystem: defaultSubsystem, + }, + }, + { + desc: "with all options", + opts: []FactoryOption{ + WithNamespace("test_namespace"), + WithSubsystem("test_subsystem"), + WithReqSizeBuckets(testBuckets), + WithRespSizeBuckets(testBuckets), + WithReqDurationBuckets(testBuckets), + }, + expected: &factoryConfig{ + namespace: "test_namespace", + subsystem: "test_subsystem", + reqSizeBuckets: testBuckets, + respSizeBuckets: testBuckets, + reqDurationBuckets: testBuckets, + }, + }, + { + desc: "with namespace", + opts: []FactoryOption{WithNamespace("test_namespace")}, + expected: &factoryConfig{ + namespace: "test_namespace", + subsystem: defaultSubsystem, + reqSizeBuckets: prometheus.DefBuckets, + respSizeBuckets: prometheus.DefBuckets, + reqDurationBuckets: prometheus.DefBuckets, + }, + }, + { + desc: "with subsystem", + opts: []FactoryOption{WithSubsystem("test_subsystem")}, + expected: &factoryConfig{ + namespace: defaultNamespace, + subsystem: "test_subsystem", + reqSizeBuckets: prometheus.DefBuckets, + respSizeBuckets: prometheus.DefBuckets, + reqDurationBuckets: prometheus.DefBuckets, + }, + }, + { + desc: "with request size buckets", + opts: []FactoryOption{WithReqSizeBuckets(testBuckets)}, + expected: &factoryConfig{ + namespace: defaultNamespace, + subsystem: defaultSubsystem, + reqSizeBuckets: testBuckets, + respSizeBuckets: prometheus.DefBuckets, + reqDurationBuckets: prometheus.DefBuckets, + }, + }, + { + desc: "with response size buckets", + opts: []FactoryOption{WithRespSizeBuckets(testBuckets)}, + expected: &factoryConfig{ + namespace: defaultNamespace, + subsystem: defaultSubsystem, + reqSizeBuckets: prometheus.DefBuckets, + respSizeBuckets: testBuckets, + reqDurationBuckets: prometheus.DefBuckets, + }, + }, + { + desc: "with request duration buckets", + opts: []FactoryOption{WithReqDurationBuckets(testBuckets)}, + expected: &factoryConfig{ + namespace: defaultNamespace, + subsystem: defaultSubsystem, + reqSizeBuckets: prometheus.DefBuckets, + respSizeBuckets: prometheus.DefBuckets, + reqDurationBuckets: testBuckets, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + cfg := applyFactoryOptions(tc.opts) + require.Equal(t, tc.expected, cfg) + }) + } +} diff --git a/metrics/grpc/factory_test.go b/metrics/grpc/factory_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b66b33bacb84eaeabcd8492a9c72018eeb1974dc --- /dev/null +++ b/metrics/grpc/factory_test.go @@ -0,0 +1,128 @@ +package metricsgrpc_test + +import ( + "errors" + "strings" + "testing" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/require" + metricsgrpc "gitlab.com/gitlab-org/labkit/metrics/grpc" +) + +// fakeRegistry is a mock prometheus.Registerer for testing error handling. +type fakeRegistry struct { + errOnRegister error + errOnRegisterFQN string +} + +func (r *fakeRegistry) Register(c prometheus.Collector) error { + if r.errOnRegister != nil { + // To get the name of the collector, we need to describe it and check the FQName + ch := make(chan *prometheus.Desc) + go func() { + c.Describe(ch) + close(ch) + }() + + for desc := range ch { + if r.errOnRegisterFQN == "" || strings.Contains(desc.String(), r.errOnRegisterFQN) { + return r.errOnRegister + } + } + } + return nil +} + +func (r *fakeRegistry) MustRegister(c ...prometheus.Collector) { + // Do nothing for the test +} + +func (r *fakeRegistry) Unregister(c prometheus.Collector) bool { + return true +} + +func (r *fakeRegistry) Gather() ([]*dto.MetricFamily, error) { + return nil, nil +} + +func TestNewServerMetricsFactory(t *testing.T) { + // Unregister all collectors to ensure a clean state for the test + prometheus.DefaultRegisterer = prometheus.NewRegistry() + + factory, err := metricsgrpc.NewServerMetricsFactory() + require.NoError(t, err) + require.NotNil(t, factory) + + // The first call should succeed + metrics, err := factory() + require.NoError(t, err) + require.NotNil(t, metrics) + + // The second call should also succeed, as the factory should handle the + // prometheus.AlreadyRegisteredError gracefully + metrics, err = factory() + require.NoError(t, err) + require.NotNil(t, metrics) +} + +func TestNewServerMetricsFactory_RegistrationError(t *testing.T) { + // We create a fake registry that will return a specific error + expectedErr := errors.New("a generic registration error") + prometheus.DefaultRegisterer = &fakeRegistry{errOnRegister: expectedErr} + defer func() { + // Restore the default registerer + prometheus.DefaultRegisterer = prometheus.NewRegistry() + }() + + factory, err := metricsgrpc.NewServerMetricsFactory() + require.NoError(t, err) + + // The factory call should now fail with our specific error + _, err = factory() + require.Error(t, err) + require.Equal(t, expectedErr, err) +} + +func TestNewServerMetricsFactory_RequestSizeHistogramError(t *testing.T) { + // We create a fake registry that will return a specific error + expectedErr := errors.New("a generic registration error") + prometheus.DefaultRegisterer = &fakeRegistry{ + errOnRegister: expectedErr, + errOnRegisterFQN: "request_size_bytes", + } + defer func() { + // Restore the default registerer + prometheus.DefaultRegisterer = prometheus.NewRegistry() + }() + + factory, err := metricsgrpc.NewServerMetricsFactory() + require.NoError(t, err) + + // The factory call should now fail with our specific error + _, err = factory() + require.Error(t, err) + require.Equal(t, expectedErr, err) +} + +func TestNewServerMetricsFactory_ResponseSizeHistogramError(t *testing.T) { + // We create a fake registry that will return a specific error + expectedErr := errors.New("a generic registration error") + prometheus.DefaultRegisterer = &fakeRegistry{ + errOnRegister: expectedErr, + errOnRegisterFQN: "response_size_bytes", + } + defer func() { + // Restore the default registerer + prometheus.DefaultRegisterer = prometheus.NewRegistry() + }() + + factory, err := metricsgrpc.NewServerMetricsFactory() + require.NoError(t, err) + + // The factory call should now fail with our specific error + _, err = factory() + require.Error(t, err) + require.Equal(t, expectedErr, err) +} diff --git a/metrics/grpc/metrics.go b/metrics/grpc/metrics.go new file mode 100644 index 0000000000000000000000000000000000000000..bf724431534d6c9264e508b0f48de76f9492103a --- /dev/null +++ b/metrics/grpc/metrics.go @@ -0,0 +1,118 @@ +package metricsgrpc + +import ( + "context" + "strings" + + middleware "github.com/grpc-ecosystem/go-grpc-middleware" + prom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" +) + +// ServerMetrics represents a set of gRPC metrics for a server. +type ServerMetrics struct { + *prom.ServerMetrics + reqSizeHistogram *prometheus.HistogramVec + respSizeHistogram *prometheus.HistogramVec +} + +// UnaryServerSizeInterceptor returns a new unary server interceptor that collects metrics. +func (m *ServerMetrics) UnaryServerSizeInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + service, method := splitMethodName(info.FullMethod) + + if p, ok := req.(proto.Message); ok { + m.reqSizeHistogram.WithLabelValues(service, method, "unary").Observe(float64(proto.Size(p))) + } + + resp, err := handler(ctx, req) + + if p, ok := resp.(proto.Message); ok { + m.respSizeHistogram.WithLabelValues(service, method, "unary").Observe(float64(proto.Size(p))) + } + + return resp, err + } +} + +// UnaryServerInterceptor returns a new unary server interceptor that collects metrics. +func (m *ServerMetrics) UnaryServerInterceptor(opts ...prom.Option) grpc.UnaryServerInterceptor { + return middleware.ChainUnaryServer( + m.ServerMetrics.UnaryServerInterceptor(opts...), + m.UnaryServerSizeInterceptor(), + ) +} + +// StreamServerInterceptor returns a new stream server interceptor that collects metrics. +func (m *ServerMetrics) StreamServerInterceptor(opts ...prom.Option) grpc.StreamServerInterceptor { + return middleware.ChainStreamServer( + m.ServerMetrics.StreamServerInterceptor(opts...), + m.StreamServerSizeInterceptor(), + ) +} + +// StreamServerSizeInterceptor returns a new stream server interceptor that collects metrics. +func (m *ServerMetrics) StreamServerSizeInterceptor() grpc.StreamServerInterceptor { + return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + service, method := splitMethodName(info.FullMethod) + streamType := streamType(info) + + wrapped := middleware.WrapServerStream(ss) + wrapped.WrappedContext = ss.Context() + + ws := &wrappedServerStream{ + ServerStream: wrapped, + reqSizeHistogram: m.reqSizeHistogram, + respSizeHistogram: m.respSizeHistogram, + service: service, + method: method, + streamType: streamType, + } + + return handler(srv, ws) + } +} + +type wrappedServerStream struct { + grpc.ServerStream + reqSizeHistogram *prometheus.HistogramVec + respSizeHistogram *prometheus.HistogramVec + service string + method string + streamType string +} + +func (w *wrappedServerStream) SendMsg(m any) error { + if p, ok := m.(proto.Message); ok { + w.respSizeHistogram.WithLabelValues(w.service, w.method, w.streamType).Observe(float64(proto.Size(p))) + } + return w.ServerStream.SendMsg(m) +} + +func (w *wrappedServerStream) RecvMsg(m any) error { + err := w.ServerStream.RecvMsg(m) + if err == nil { + if p, ok := m.(proto.Message); ok { + w.reqSizeHistogram.WithLabelValues(w.service, w.method, w.streamType).Observe(float64(proto.Size(p))) + } + } + return err +} + +func streamType(info *grpc.StreamServerInfo) string { + if info.IsClientStream && !info.IsServerStream { + return "client_stream" + } else if !info.IsClientStream && info.IsServerStream { + return "server_stream" + } + return "bidi_stream" +} + +func splitMethodName(fullMethodName string) (string, string) { + if i := strings.LastIndex(fullMethodName, "/"); i >= 0 { + return fullMethodName[1:i], fullMethodName[i+1:] + } + return "unknown", "unknown" +} diff --git a/metrics/grpc/metrics_test.go b/metrics/grpc/metrics_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b2654240f134cdd5b81ac0a611a8bb4678177524 --- /dev/null +++ b/metrics/grpc/metrics_test.go @@ -0,0 +1,89 @@ +package metricsgrpc_test + +import ( + "context" + "net" + "testing" + + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/require" + metricsgrpc "gitlab.com/gitlab-org/labkit/metrics/grpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/health/grpc_health_v1" +) + +func TestInterceptors(t *testing.T) { + factory, err := metricsgrpc.NewServerMetricsFactory() + require.NoError(t, err) + + metrics, err := factory() + require.NoError(t, err) + + server := grpc.NewServer( + grpc.UnaryInterceptor(metrics.UnaryServerInterceptor()), + grpc.StreamInterceptor(metrics.StreamServerInterceptor()), + ) + grpc_health_v1.RegisterHealthServer(server, &healthService{}) + + lis, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + + go server.Serve(lis) + defer server.Stop() + + conn, err := grpc.Dial(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + defer conn.Close() + + client := grpc_health_v1.NewHealthClient(conn) + + t.Run("Unary", func(t *testing.T) { + _, err := client.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{}) + require.NoError(t, err) + + require.Equal(t, 1, testutil.CollectAndCount(metrics.ServerMetrics, "gitlab_grpc_server_handled_total")) + require.Equal(t, 1, testutil.CollectAndCount(metrics.ServerMetrics, "gitlab_grpc_server_handling_seconds")) + }) + + t.Run("Stream", func(t *testing.T) { + stream, err := client.Watch(context.Background(), &grpc_health_v1.HealthCheckRequest{}) + require.NoError(t, err) + + // The server sends a single response, then we close the stream. + _, err = stream.Recv() + require.NoError(t, err) + + require.NoError(t, stream.CloseSend()) + + // We need to drain the stream to ensure the interceptor has finished. + for { + if _, err := stream.Recv(); err != nil { + break + } + } + + require.Equal(t, 2, testutil.CollectAndCount(metrics.ServerMetrics, "gitlab_grpc_server_handled_total")) + require.Equal(t, 2, testutil.CollectAndCount(metrics.ServerMetrics, "gitlab_grpc_server_handling_seconds")) + }) +} + +type healthService struct { + grpc_health_v1.UnimplementedHealthServer +} + +func (s *healthService) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { + return &grpc_health_v1.HealthCheckResponse{Status: grpc_health_v1.HealthCheckResponse_SERVING}, nil +} + +func (s *healthService) Watch(req *grpc_health_v1.HealthCheckRequest, stream grpc_health_v1.Health_WatchServer) error { + stream.Send(&grpc_health_v1.HealthCheckResponse{Status: grpc_health_v1.HealthCheckResponse_SERVING}) + + // Keep the stream open until the client closes it. + for { + // The client will close the stream, and RecvMsg will return an error. + if err := stream.RecvMsg(new(grpc_health_v1.HealthCheckRequest)); err != nil { + return err + } + } +}