github.com/weaveworks/common@v0.0.0-20230728070032-dd9e68f319d5/middleware/grpc_stats_test.go (about) 1 package middleware 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/rand" 7 "net" 8 "testing" 9 "time" 10 11 "github.com/golang/protobuf/proto" 12 "github.com/prometheus/client_golang/prometheus" 13 "github.com/prometheus/client_golang/prometheus/testutil" 14 "github.com/stretchr/testify/require" 15 "google.golang.org/grpc" 16 "google.golang.org/grpc/credentials/insecure" 17 "google.golang.org/grpc/health" 18 "google.golang.org/grpc/health/grpc_health_v1" 19 20 "github.com/weaveworks/common/middleware/middleware_test" 21 ) 22 23 func TestGrpcStats(t *testing.T) { 24 reg := prometheus.NewRegistry() 25 26 received := prometheus.NewHistogramVec(prometheus.HistogramOpts{ 27 Name: "received_payload_bytes", 28 Help: "Size of received gRPC messages", 29 Buckets: BodySizeBuckets, 30 }, []string{"method", "route"}) 31 require.NoError(t, reg.Register(received)) 32 33 sent := prometheus.NewHistogramVec(prometheus.HistogramOpts{ 34 Name: "sent_payload_bytes", 35 Help: "Size of sent gRPC", 36 Buckets: BodySizeBuckets, 37 }, []string{"method", "route"}) 38 require.NoError(t, reg.Register(sent)) 39 40 inflightRequests := prometheus.NewGaugeVec(prometheus.GaugeOpts{ 41 Name: "inflight_requests", 42 Help: "Current number of inflight requests.", 43 }, []string{"method", "route"}) 44 require.NoError(t, reg.Register(inflightRequests)) 45 46 stats := NewStatsHandler(received, sent, inflightRequests) 47 48 serv := grpc.NewServer(grpc.StatsHandler(stats), grpc.MaxRecvMsgSize(10e6)) 49 defer serv.GracefulStop() 50 51 listener, err := net.Listen("tcp", "localhost:0") 52 require.NoError(t, err) 53 54 go func() { 55 require.NoError(t, serv.Serve(listener)) 56 }() 57 58 grpc_health_v1.RegisterHealthServer(serv, health.NewServer()) 59 60 closed := false 61 conn, err := grpc.Dial(listener.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) 62 require.NoError(t, err) 63 defer func() { 64 if !closed { 65 require.NoError(t, conn.Close()) 66 } 67 }() 68 69 hc := grpc_health_v1.NewHealthClient(conn) 70 71 // First request (empty). 72 resp, err := hc.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{}) 73 require.NoError(t, err) 74 require.Equal(t, grpc_health_v1.HealthCheckResponse_SERVING, resp.Status) 75 76 // Second request, with large service name. This returns error, which doesn't count as "payload". 77 _, err = hc.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{ 78 Service: generateString(8 * 1024 * 1024), 79 }) 80 require.EqualError(t, err, "rpc error: code = NotFound desc = unknown service") 81 82 err = testutil.GatherAndCompare(reg, bytes.NewBufferString(` 83 # HELP received_payload_bytes Size of received gRPC messages 84 # TYPE received_payload_bytes histogram 85 received_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="1.048576e+06"} 1 86 received_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="2.62144e+06"} 1 87 received_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="5.24288e+06"} 1 88 received_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="1.048576e+07"} 2 89 received_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="2.62144e+07"} 2 90 received_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="5.24288e+07"} 2 91 received_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="1.048576e+08"} 2 92 received_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="2.62144e+08"} 2 93 received_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="+Inf"} 2 94 received_payload_bytes_sum{method="gRPC", route="/grpc.health.v1.Health/Check"} 8.388623e+06 95 received_payload_bytes_count{method="gRPC", route="/grpc.health.v1.Health/Check"} 2 96 97 # HELP sent_payload_bytes Size of sent gRPC 98 # TYPE sent_payload_bytes histogram 99 sent_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="1.048576e+06"} 1 100 sent_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="2.62144e+06"} 1 101 sent_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="5.24288e+06"} 1 102 sent_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="1.048576e+07"} 1 103 sent_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="2.62144e+07"} 1 104 sent_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="5.24288e+07"} 1 105 sent_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="1.048576e+08"} 1 106 sent_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="2.62144e+08"} 1 107 sent_payload_bytes_bucket{method="gRPC", route="/grpc.health.v1.Health/Check",le="+Inf"} 1 108 sent_payload_bytes_sum{method="gRPC", route="/grpc.health.v1.Health/Check"} 7 109 sent_payload_bytes_count{method="gRPC", route="/grpc.health.v1.Health/Check"} 1 110 `), "received_payload_bytes", "sent_payload_bytes") 111 require.NoError(t, err) 112 113 closed = true 114 require.NoError(t, conn.Close()) 115 } 116 117 func TestGrpcStatsStreaming(t *testing.T) { 118 reg := prometheus.NewRegistry() 119 120 received := prometheus.NewHistogramVec(prometheus.HistogramOpts{ 121 Name: "received_payload_bytes", 122 Help: "Size of received gRPC messages", 123 Buckets: BodySizeBuckets, 124 }, []string{"method", "route"}) 125 require.NoError(t, reg.Register(received)) 126 127 sent := prometheus.NewHistogramVec(prometheus.HistogramOpts{ 128 Name: "sent_payload_bytes", 129 Help: "Size of sent gRPC", 130 Buckets: BodySizeBuckets, 131 }, []string{"method", "route"}) 132 require.NoError(t, reg.Register(sent)) 133 134 inflightRequests := prometheus.NewGaugeVec(prometheus.GaugeOpts{ 135 Name: "inflight_requests", 136 Help: "Current number of inflight requests.", 137 }, []string{"method", "route"}) 138 require.NoError(t, reg.Register(inflightRequests)) 139 140 stats := NewStatsHandler(received, sent, inflightRequests) 141 142 serv := grpc.NewServer(grpc.StatsHandler(stats), grpc.MaxSendMsgSize(10e6), grpc.MaxRecvMsgSize(10e6)) 143 defer serv.GracefulStop() 144 145 listener, err := net.Listen("tcp", "localhost:0") 146 require.NoError(t, err) 147 148 go func() { 149 require.NoError(t, serv.Serve(listener)) 150 }() 151 152 middleware_test.RegisterEchoServerServer(serv, &halfEcho{log: t.Log}) 153 154 conn, err := grpc.Dial(listener.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(10e6), grpc.MaxCallSendMsgSize(10e6))) 155 require.NoError(t, err) 156 defer func() { 157 require.NoError(t, conn.Close()) 158 }() 159 160 fc := middleware_test.NewEchoServerClient(conn) 161 162 s, err := fc.Process(context.Background()) 163 require.NoError(t, err) 164 165 for ix := 0; ix < 5; ix++ { 166 msg := &middleware_test.Msg{ 167 Body: []byte(generateString((ix + 1) * 1024 * 1024)), 168 } 169 170 t.Log("Client Sending", proto.Size(msg)) 171 err = s.Send(msg) 172 require.NoError(t, err) 173 174 _, err := s.Recv() 175 require.NoError(t, err) 176 177 err = testutil.GatherAndCompare(reg, bytes.NewBufferString(` 178 # HELP inflight_requests Current number of inflight requests. 179 # TYPE inflight_requests gauge 180 inflight_requests{method="gRPC", route="/middleware.EchoServer/Process"} 1 181 `), "inflight_requests") 182 require.NoError(t, err) 183 } 184 require.NoError(t, s.CloseSend()) 185 186 // Wait for inflight_requests to go to 0. 187 timeout := 1 * time.Second 188 sleep := timeout / 10 189 190 for endTime := time.Now().Add(timeout); time.Now().Before(endTime); { 191 err = testutil.GatherAndCompare(reg, bytes.NewBufferString(` 192 # HELP inflight_requests Current number of inflight requests. 193 # TYPE inflight_requests gauge 194 inflight_requests{method="gRPC", route="/middleware.EchoServer/Process"} 0 195 `), "inflight_requests") 196 if err == nil { 197 break 198 } 199 time.Sleep(sleep) 200 } 201 require.NoError(t, err) 202 203 err = testutil.GatherAndCompare(reg, bytes.NewBufferString(` 204 # HELP received_payload_bytes Size of received gRPC messages 205 # TYPE received_payload_bytes histogram 206 received_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="1.048576e+06"} 0 207 received_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="2.62144e+06"} 2 208 received_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="5.24288e+06"} 4 209 received_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="1.048576e+07"} 5 210 received_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="2.62144e+07"} 5 211 received_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="5.24288e+07"} 5 212 received_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="1.048576e+08"} 5 213 received_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="2.62144e+08"} 5 214 received_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="+Inf"} 5 215 received_payload_bytes_sum{method="gRPC",route="/middleware.EchoServer/Process"} 1.5728689e+07 216 received_payload_bytes_count{method="gRPC",route="/middleware.EchoServer/Process"} 5 217 218 # HELP sent_payload_bytes Size of sent gRPC 219 # TYPE sent_payload_bytes histogram 220 sent_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="1.048576e+06"} 1 221 sent_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="2.62144e+06"} 4 222 sent_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="5.24288e+06"} 5 223 sent_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="1.048576e+07"} 5 224 sent_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="2.62144e+07"} 5 225 sent_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="5.24288e+07"} 5 226 sent_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="1.048576e+08"} 5 227 sent_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="2.62144e+08"} 5 228 sent_payload_bytes_bucket{method="gRPC",route="/middleware.EchoServer/Process",le="+Inf"} 5 229 sent_payload_bytes_sum{method="gRPC",route="/middleware.EchoServer/Process"} 7.864367e+06 230 sent_payload_bytes_count{method="gRPC",route="/middleware.EchoServer/Process"} 5 231 `), "received_payload_bytes", "sent_payload_bytes") 232 233 require.NoError(t, err) 234 } 235 236 type halfEcho struct { 237 log func(args ...interface{}) 238 } 239 240 func (f halfEcho) Process(server middleware_test.EchoServer_ProcessServer) error { 241 for { 242 msg, err := server.Recv() 243 if err != nil { 244 return err 245 } 246 247 // Half the body 248 msg.Body = msg.Body[:len(msg.Body)/2] 249 250 f.log("Server Sending", proto.Size(msg)) 251 err = server.Send(msg) 252 if err != nil { 253 return err 254 } 255 } 256 } 257 258 func generateString(size int) string { 259 // Use random bytes, to avoid compression. 260 buf := make([]byte, size) 261 _, err := rand.Read(buf) 262 if err != nil { 263 // Should not happen. 264 panic(err) 265 } 266 267 // To avoid invalid UTF-8 sequences (which protobuf complains about), we cleanup the data a bit. 268 for ix, b := range buf { 269 if b < ' ' { 270 b += ' ' 271 } 272 b = b & 0x7f 273 buf[ix] = b 274 } 275 return string(buf) 276 }