go.uber.org/yarpc@v1.72.1/bench_test.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package yarpc_test 22 23 import ( 24 "bytes" 25 "context" 26 "io" 27 "io/ioutil" 28 "net" 29 "net/http" 30 "testing" 31 "time" 32 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 "github.com/uber/tchannel-go" 36 traw "github.com/uber/tchannel-go/raw" 37 "go.uber.org/yarpc" 38 "go.uber.org/yarpc/api/transport" 39 "go.uber.org/yarpc/encoding/raw" 40 yhttp "go.uber.org/yarpc/transport/http" 41 ytchannel "go.uber.org/yarpc/transport/tchannel" 42 ncontext "golang.org/x/net/context" 43 ) 44 45 var _reqBody = []byte("hello") 46 47 func yarpcEcho(ctx context.Context, body []byte) ([]byte, error) { 48 call := yarpc.CallFromContext(ctx) 49 for _, k := range call.HeaderNames() { 50 if err := call.WriteResponseHeader(k, call.Header(k)); err != nil { 51 return nil, err 52 } 53 } 54 return body, nil 55 } 56 57 func httpEcho(t testing.TB) http.HandlerFunc { 58 return func(w http.ResponseWriter, r *http.Request) { 59 defer r.Body.Close() 60 hs := w.Header() 61 for k, vs := range r.Header { 62 hs[k] = vs 63 } 64 65 _, err := io.Copy(w, r.Body) 66 if err != nil { 67 t.Errorf("failed to write HTTP response body: %v", err) 68 } 69 } 70 } 71 72 type tchannelEcho struct{ t testing.TB } 73 74 func (tchannelEcho) Handle(ctx ncontext.Context, args *traw.Args) (*traw.Res, error) { 75 return &traw.Res{Arg2: args.Arg2, Arg3: args.Arg3}, nil 76 } 77 78 func (t tchannelEcho) OnError(ctx ncontext.Context, err error) { 79 t.t.Fatalf("request failed: %v", err) 80 } 81 82 func withDispatcher(t testing.TB, cfg yarpc.Config, f func(*yarpc.Dispatcher), ps ...[]transport.Procedure) { 83 d := yarpc.NewDispatcher(cfg) 84 for _, p := range ps { 85 d.Register(p) 86 } 87 require.NoError(t, d.Start(), "failed to start server") 88 defer d.Stop() 89 90 f(d) 91 } 92 93 func withHTTPServer(t testing.TB, listenOn string, h http.Handler, f func()) { 94 l, err := net.Listen("tcp", listenOn) 95 require.NoError(t, err, "could not listen on %q", listenOn) 96 97 ch := make(chan struct{}) 98 go func() { 99 http.Serve(l, h) 100 close(ch) 101 }() 102 f() 103 assert.NoError(t, l.Close(), "failed to stop listener on %q", listenOn) 104 <-ch // wait until server has stopped 105 } 106 107 func runYARPCClient(b *testing.B, c raw.Client) { 108 for i := 0; i < b.N; i++ { 109 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) 110 defer cancel() 111 _, err := c.Call(ctx, "echo", _reqBody) 112 if err != nil { 113 b.Errorf("request %d failed: %v", i+1, err) 114 } 115 } 116 } 117 118 func runHTTPClient(b *testing.B, c *http.Client, url string) { 119 for i := 0; i < b.N; i++ { 120 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) 121 defer cancel() 122 req, err := http.NewRequest("POST", url, bytes.NewReader(_reqBody)) 123 if err != nil { 124 b.Errorf("failed to build request %d: %v", i+1, err) 125 } 126 req = req.WithContext(ctx) 127 128 req.Header = http.Header{ 129 "Context-TTL-MS": {"100"}, 130 "Rpc-Caller": {"http-client"}, 131 "Rpc-Encoding": {"raw"}, 132 "Rpc-Procedure": {"echo"}, 133 "Rpc-Service": {"server"}, 134 } 135 res, err := c.Do(req) 136 if err != nil { 137 b.Errorf("request %d failed: %v", i+1, err) 138 } 139 140 if _, err := ioutil.ReadAll(res.Body); err != nil { 141 b.Errorf("failed to read response %d: %v", i+1, err) 142 } 143 if err := res.Body.Close(); err != nil { 144 b.Errorf("failed to close response body %d: %v", i+1, err) 145 } 146 } 147 } 148 149 func runTChannelClient(b *testing.B, c *tchannel.Channel, hostPort string) { 150 headers := []byte{0x00, 0x00} // TODO: YARPC TChannel should support empty arg2 151 for i := 0; i < b.N; i++ { 152 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) 153 defer cancel() 154 call, err := c.BeginCall(ctx, hostPort, "server", "echo", 155 &tchannel.CallOptions{Format: tchannel.Raw}) 156 157 if err != nil { 158 b.Errorf("BeginCall %v failed: %v", i+1, err) 159 } 160 161 _, _, _, err = traw.WriteArgs(call, headers, _reqBody) 162 if err != nil { 163 b.Errorf("request %v failed: %v", i+1, err) 164 } 165 } 166 } 167 168 func Benchmark_HTTP_YARPCToYARPC(b *testing.B) { 169 httpTransport := yhttp.NewTransport() 170 serverCfg := yarpc.Config{ 171 Name: "server", 172 Inbounds: yarpc.Inbounds{httpTransport.NewInbound("127.0.0.1:8999")}, 173 } 174 175 clientCfg := yarpc.Config{ 176 Name: "client", 177 Outbounds: yarpc.Outbounds{ 178 "server": { 179 Unary: httpTransport.NewSingleOutbound("http://localhost:8999"), 180 }, 181 }, 182 } 183 184 withDispatcher( 185 b, serverCfg, 186 func(server *yarpc.Dispatcher) { 187 withDispatcher(b, clientCfg, func(client *yarpc.Dispatcher) { 188 b.ResetTimer() 189 runYARPCClient(b, raw.New(client.ClientConfig("server"))) 190 }) 191 }, 192 raw.Procedure("echo", yarpcEcho), 193 ) 194 } 195 196 func Benchmark_HTTP_YARPCToNetHTTP(b *testing.B) { 197 httpTransport := yhttp.NewTransport() 198 clientCfg := yarpc.Config{ 199 Name: "client", 200 Outbounds: yarpc.Outbounds{ 201 "server": { 202 Unary: httpTransport.NewSingleOutbound("http://localhost:8998"), 203 }, 204 }, 205 } 206 207 withHTTPServer(b, ":8998", httpEcho(b), func() { 208 withDispatcher(b, clientCfg, func(client *yarpc.Dispatcher) { 209 b.ResetTimer() 210 runYARPCClient(b, raw.New(client.ClientConfig("server"))) 211 }) 212 }) 213 } 214 215 func Benchmark_HTTP_NetHTTPToYARPC(b *testing.B) { 216 httpTransport := yhttp.NewTransport() 217 serverCfg := yarpc.Config{ 218 Name: "server", 219 Inbounds: yarpc.Inbounds{httpTransport.NewInbound("127.0.0.1:8996")}, 220 } 221 222 withDispatcher( 223 b, serverCfg, func(server *yarpc.Dispatcher) { 224 b.ResetTimer() 225 runHTTPClient(b, http.DefaultClient, "http://localhost:8996") 226 }, 227 raw.Procedure("echo", yarpcEcho), 228 ) 229 } 230 231 func Benchmark_HTTP_NetHTTPToNetHTTP(b *testing.B) { 232 withHTTPServer(b, ":8997", httpEcho(b), func() { 233 b.ResetTimer() 234 runHTTPClient(b, http.DefaultClient, "http://localhost:8997") 235 }) 236 } 237 238 func Benchmark_TChannel_YARPCToYARPC(b *testing.B) { 239 serverTChannel, err := ytchannel.NewChannelTransport(ytchannel.ServiceName("server")) 240 require.NoError(b, err) 241 242 serverCfg := yarpc.Config{ 243 Name: "server", 244 Inbounds: yarpc.Inbounds{serverTChannel.NewInbound()}, 245 } 246 247 clientTChannel, err := ytchannel.NewChannelTransport(ytchannel.ServiceName("client")) 248 require.NoError(b, err) 249 250 // no defer close on channels because YARPC will take care of that 251 252 withDispatcher( 253 b, serverCfg, func(server *yarpc.Dispatcher) { 254 // Need server already started to build client config 255 clientCfg := yarpc.Config{ 256 Name: "client", 257 Outbounds: yarpc.Outbounds{ 258 "server": { 259 Unary: clientTChannel.NewSingleOutbound(serverTChannel.ListenAddr()), 260 }, 261 }, 262 } 263 withDispatcher(b, clientCfg, func(client *yarpc.Dispatcher) { 264 b.ResetTimer() 265 runYARPCClient(b, raw.New(client.ClientConfig("server"))) 266 }) 267 }, 268 raw.Procedure("echo", yarpcEcho), 269 ) 270 } 271 272 func Benchmark_TChannel_YARPCToTChannel(b *testing.B) { 273 serverCh, err := tchannel.NewChannel("server", nil) 274 require.NoError(b, err, "failed to build server TChannel") 275 defer serverCh.Close() 276 277 serverCh.Register(traw.Wrap(tchannelEcho{t: b}), "echo") 278 require.NoError(b, serverCh.ListenAndServe("127.0.0.1:0"), "failed to start up TChannel") 279 280 clientTChannel, err := ytchannel.NewChannelTransport(ytchannel.ServiceName("client")) 281 require.NoError(b, err) 282 283 clientCfg := yarpc.Config{ 284 Name: "client", 285 Outbounds: yarpc.Outbounds{ 286 "server": { 287 Unary: clientTChannel.NewSingleOutbound(serverCh.PeerInfo().HostPort), 288 }, 289 }, 290 } 291 292 withDispatcher(b, clientCfg, func(client *yarpc.Dispatcher) { 293 b.ResetTimer() 294 runYARPCClient(b, raw.New(client.ClientConfig("server"))) 295 }) 296 } 297 298 func Benchmark_TChannel_TChannelToYARPC(b *testing.B) { 299 tchannelTransport, err := ytchannel.NewChannelTransport(ytchannel.ServiceName("server")) 300 require.NoError(b, err) 301 302 serverCfg := yarpc.Config{ 303 Name: "server", 304 Inbounds: yarpc.Inbounds{tchannelTransport.NewInbound()}, 305 } 306 307 withDispatcher( 308 b, serverCfg, func(dispatcher *yarpc.Dispatcher) { 309 310 clientCh, err := tchannel.NewChannel("client", nil) 311 require.NoError(b, err, "failed to build client TChannel") 312 defer clientCh.Close() 313 314 b.ResetTimer() 315 runTChannelClient(b, clientCh, tchannelTransport.ListenAddr()) 316 }, 317 raw.Procedure("echo", yarpcEcho), 318 ) 319 } 320 321 func Benchmark_TChannel_TChannelToTChannel(b *testing.B) { 322 serverCh, err := tchannel.NewChannel("server", nil) 323 require.NoError(b, err, "failed to build server TChannel") 324 defer serverCh.Close() 325 326 serverCh.Register(traw.Wrap(tchannelEcho{t: b}), "echo") 327 require.NoError(b, serverCh.ListenAndServe("127.0.0.1:0"), "failed to start up TChannel") 328 329 clientCh, err := tchannel.NewChannel("client", nil) 330 require.NoError(b, err, "failed to build client TChannel") 331 defer clientCh.Close() 332 333 b.ResetTimer() 334 runTChannelClient(b, clientCh, serverCh.PeerInfo().HostPort) 335 } 336 337 func BenchmarkHTTPRoundTripper(b *testing.B) { 338 uri := "http://localhost:8001" 339 340 outbound := yhttp.NewTransport().NewSingleOutbound(uri) 341 require.NoError(b, outbound.Start()) 342 defer outbound.Stop() 343 344 roundTripper := &http.Client{Transport: outbound} 345 346 withHTTPServer(b, ":8001", httpEcho(b), func() { 347 b.ResetTimer() 348 runHTTPClient(b, roundTripper, uri) 349 }) 350 }