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  }