benchmark.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /*
  2. *
  3. * Copyright 2014 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. /*
  19. Package benchmark implements the building blocks to setup end-to-end gRPC benchmarks.
  20. */
  21. package benchmark
  22. import (
  23. "context"
  24. "fmt"
  25. "io"
  26. "log"
  27. "net"
  28. "google.golang.org/grpc"
  29. "google.golang.org/grpc/codes"
  30. "google.golang.org/grpc/grpclog"
  31. "google.golang.org/grpc/metadata"
  32. "google.golang.org/grpc/status"
  33. testgrpc "google.golang.org/grpc/interop/grpc_testing"
  34. testpb "google.golang.org/grpc/interop/grpc_testing"
  35. )
  36. var logger = grpclog.Component("benchmark")
  37. // Allows reuse of the same testpb.Payload object.
  38. func setPayload(p *testpb.Payload, t testpb.PayloadType, size int) {
  39. if size < 0 {
  40. logger.Fatalf("Requested a response with invalid length %d", size)
  41. }
  42. body := make([]byte, size)
  43. switch t {
  44. case testpb.PayloadType_COMPRESSABLE:
  45. default:
  46. logger.Fatalf("Unsupported payload type: %d", t)
  47. }
  48. p.Type = t
  49. p.Body = body
  50. }
  51. // NewPayload creates a payload with the given type and size.
  52. func NewPayload(t testpb.PayloadType, size int) *testpb.Payload {
  53. p := new(testpb.Payload)
  54. setPayload(p, t, size)
  55. return p
  56. }
  57. type testServer struct {
  58. testgrpc.UnimplementedBenchmarkServiceServer
  59. }
  60. func (s *testServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
  61. return &testpb.SimpleResponse{
  62. Payload: NewPayload(in.ResponseType, int(in.ResponseSize)),
  63. }, nil
  64. }
  65. // UnconstrainedStreamingHeader indicates to the StreamingCall handler that its
  66. // behavior should be unconstrained (constant send/receive in parallel) instead
  67. // of ping-pong.
  68. const UnconstrainedStreamingHeader = "unconstrained-streaming"
  69. func (s *testServer) StreamingCall(stream testgrpc.BenchmarkService_StreamingCallServer) error {
  70. if md, ok := metadata.FromIncomingContext(stream.Context()); ok && len(md[UnconstrainedStreamingHeader]) != 0 {
  71. return s.UnconstrainedStreamingCall(stream)
  72. }
  73. response := &testpb.SimpleResponse{
  74. Payload: new(testpb.Payload),
  75. }
  76. in := new(testpb.SimpleRequest)
  77. for {
  78. // use ServerStream directly to reuse the same testpb.SimpleRequest object
  79. err := stream.(grpc.ServerStream).RecvMsg(in)
  80. if err == io.EOF {
  81. // read done.
  82. return nil
  83. }
  84. if err != nil {
  85. return err
  86. }
  87. setPayload(response.Payload, in.ResponseType, int(in.ResponseSize))
  88. if err := stream.Send(response); err != nil {
  89. return err
  90. }
  91. }
  92. }
  93. func (s *testServer) UnconstrainedStreamingCall(stream testgrpc.BenchmarkService_StreamingCallServer) error {
  94. in := new(testpb.SimpleRequest)
  95. // Receive a message to learn response type and size.
  96. err := stream.RecvMsg(in)
  97. if err == io.EOF {
  98. // read done.
  99. return nil
  100. }
  101. if err != nil {
  102. return err
  103. }
  104. response := &testpb.SimpleResponse{
  105. Payload: new(testpb.Payload),
  106. }
  107. setPayload(response.Payload, in.ResponseType, int(in.ResponseSize))
  108. go func() {
  109. for {
  110. // Using RecvMsg rather than Recv to prevent reallocation of SimpleRequest.
  111. err := stream.RecvMsg(in)
  112. switch status.Code(err) {
  113. case codes.Canceled:
  114. return
  115. case codes.OK:
  116. default:
  117. log.Fatalf("server recv error: %v", err)
  118. }
  119. }
  120. }()
  121. go func() {
  122. for {
  123. err := stream.Send(response)
  124. switch status.Code(err) {
  125. case codes.Unavailable:
  126. return
  127. case codes.OK:
  128. default:
  129. log.Fatalf("server send error: %v", err)
  130. }
  131. }
  132. }()
  133. <-stream.Context().Done()
  134. return stream.Context().Err()
  135. }
  136. // byteBufServer is a gRPC server that sends and receives byte buffer.
  137. // The purpose is to benchmark the gRPC performance without protobuf serialization/deserialization overhead.
  138. type byteBufServer struct {
  139. testgrpc.UnimplementedBenchmarkServiceServer
  140. respSize int32
  141. }
  142. // UnaryCall is an empty function and is not used for benchmark.
  143. // If bytebuf UnaryCall benchmark is needed later, the function body needs to be updated.
  144. func (s *byteBufServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
  145. return &testpb.SimpleResponse{}, nil
  146. }
  147. func (s *byteBufServer) StreamingCall(stream testgrpc.BenchmarkService_StreamingCallServer) error {
  148. for {
  149. var in []byte
  150. err := stream.(grpc.ServerStream).RecvMsg(&in)
  151. if err == io.EOF {
  152. return nil
  153. }
  154. if err != nil {
  155. return err
  156. }
  157. out := make([]byte, s.respSize)
  158. if err := stream.(grpc.ServerStream).SendMsg(&out); err != nil {
  159. return err
  160. }
  161. }
  162. }
  163. // ServerInfo contains the information to create a gRPC benchmark server.
  164. type ServerInfo struct {
  165. // Type is the type of the server.
  166. // It should be "protobuf" or "bytebuf".
  167. Type string
  168. // Metadata is an optional configuration.
  169. // For "protobuf", it's ignored.
  170. // For "bytebuf", it should be an int representing response size.
  171. Metadata interface{}
  172. // Listener is the network listener for the server to use
  173. Listener net.Listener
  174. }
  175. // StartServer starts a gRPC server serving a benchmark service according to info.
  176. // It returns a function to stop the server.
  177. func StartServer(info ServerInfo, opts ...grpc.ServerOption) func() {
  178. s := grpc.NewServer(opts...)
  179. switch info.Type {
  180. case "protobuf":
  181. testgrpc.RegisterBenchmarkServiceServer(s, &testServer{})
  182. case "bytebuf":
  183. respSize, ok := info.Metadata.(int32)
  184. if !ok {
  185. logger.Fatalf("failed to StartServer, invalid metadata: %v, for Type: %v", info.Metadata, info.Type)
  186. }
  187. testgrpc.RegisterBenchmarkServiceServer(s, &byteBufServer{respSize: respSize})
  188. default:
  189. logger.Fatalf("failed to StartServer, unknown Type: %v", info.Type)
  190. }
  191. go s.Serve(info.Listener)
  192. return func() {
  193. s.Stop()
  194. }
  195. }
  196. // DoUnaryCall performs an unary RPC with given stub and request and response sizes.
  197. func DoUnaryCall(tc testgrpc.BenchmarkServiceClient, reqSize, respSize int) error {
  198. pl := NewPayload(testpb.PayloadType_COMPRESSABLE, reqSize)
  199. req := &testpb.SimpleRequest{
  200. ResponseType: pl.Type,
  201. ResponseSize: int32(respSize),
  202. Payload: pl,
  203. }
  204. if _, err := tc.UnaryCall(context.Background(), req); err != nil {
  205. return fmt.Errorf("/BenchmarkService/UnaryCall(_, _) = _, %v, want _, <nil>", err)
  206. }
  207. return nil
  208. }
  209. // DoStreamingRoundTrip performs a round trip for a single streaming rpc.
  210. func DoStreamingRoundTrip(stream testgrpc.BenchmarkService_StreamingCallClient, reqSize, respSize int) error {
  211. pl := NewPayload(testpb.PayloadType_COMPRESSABLE, reqSize)
  212. req := &testpb.SimpleRequest{
  213. ResponseType: pl.Type,
  214. ResponseSize: int32(respSize),
  215. Payload: pl,
  216. }
  217. if err := stream.Send(req); err != nil {
  218. return fmt.Errorf("/BenchmarkService/StreamingCall.Send(_) = %v, want <nil>", err)
  219. }
  220. if _, err := stream.Recv(); err != nil {
  221. // EOF is a valid error here.
  222. if err == io.EOF {
  223. return nil
  224. }
  225. return fmt.Errorf("/BenchmarkService/StreamingCall.Recv(_) = %v, want <nil>", err)
  226. }
  227. return nil
  228. }
  229. // DoByteBufStreamingRoundTrip performs a round trip for a single streaming rpc, using a custom codec for byte buffer.
  230. func DoByteBufStreamingRoundTrip(stream testgrpc.BenchmarkService_StreamingCallClient, reqSize, respSize int) error {
  231. out := make([]byte, reqSize)
  232. if err := stream.(grpc.ClientStream).SendMsg(&out); err != nil {
  233. return fmt.Errorf("/BenchmarkService/StreamingCall.(ClientStream).SendMsg(_) = %v, want <nil>", err)
  234. }
  235. var in []byte
  236. if err := stream.(grpc.ClientStream).RecvMsg(&in); err != nil {
  237. // EOF is a valid error here.
  238. if err == io.EOF {
  239. return nil
  240. }
  241. return fmt.Errorf("/BenchmarkService/StreamingCall.(ClientStream).RecvMsg(_) = %v, want <nil>", err)
  242. }
  243. return nil
  244. }
  245. // NewClientConn creates a gRPC client connection to addr.
  246. func NewClientConn(addr string, opts ...grpc.DialOption) *grpc.ClientConn {
  247. return NewClientConnWithContext(context.Background(), addr, opts...)
  248. }
  249. // NewClientConnWithContext creates a gRPC client connection to addr using ctx.
  250. func NewClientConnWithContext(ctx context.Context, addr string, opts ...grpc.DialOption) *grpc.ClientConn {
  251. conn, err := grpc.DialContext(ctx, addr, opts...)
  252. if err != nil {
  253. logger.Fatalf("NewClientConn(%q) failed to create a ClientConn: %v", addr, err)
  254. }
  255. return conn
  256. }