gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/benchmark/benchmain/main.go (about)

     1  /*
     2   *
     3   * Copyright 2017 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  /*
    20  Package main provides benchmark with setting flags.
    21  
    22  An example to run some benchmarks with profiling enabled:
    23  
    24  	go run benchmark/benchmain/main.go -benchtime=10s -workloads=all \
    25  	  -compression=gzip -maxConcurrentCalls=1 -trace=off \
    26  	  -reqSizeBytes=1,1048576 -respSizeBytes=1,1048576 -networkMode=Local \
    27  	  -cpuProfile=cpuProf -memProfile=memProf -memProfileRate=10000 -resultFile=result
    28  
    29  As a suggestion, when creating a branch, you can run this benchmark and save the result
    30  file "-resultFile=basePerf", and later when you at the middle of the work or finish the
    31  work, you can get the benchmark result and compare it with the base anytime.
    32  
    33  Assume there are two result files names as "basePerf" and "curPerf" created by adding
    34  -resultFile=basePerf and -resultFile=curPerf.
    35  
    36  		To format the curPerf, run:
    37  	  	go run benchmark/benchresult/main.go curPerf
    38  		To observe how the performance changes based on a base result, run:
    39  	  	go run benchmark/benchresult/main.go basePerf curPerf
    40  */
    41  package main
    42  
    43  import (
    44  	"context"
    45  	"encoding/gob"
    46  	"flag"
    47  	"fmt"
    48  	"io"
    49  	"io/ioutil"
    50  	"log"
    51  	"net"
    52  	"os"
    53  	"reflect"
    54  	"runtime"
    55  	"runtime/pprof"
    56  	"strings"
    57  	"sync"
    58  	"sync/atomic"
    59  	"time"
    60  
    61  	grpc "gitee.com/ks-custle/core-gm/grpc"
    62  	"gitee.com/ks-custle/core-gm/grpc/benchmark"
    63  	bm "gitee.com/ks-custle/core-gm/grpc/benchmark"
    64  	"gitee.com/ks-custle/core-gm/grpc/benchmark/flags"
    65  	"gitee.com/ks-custle/core-gm/grpc/benchmark/latency"
    66  	"gitee.com/ks-custle/core-gm/grpc/benchmark/stats"
    67  	"gitee.com/ks-custle/core-gm/grpc/grpclog"
    68  	"gitee.com/ks-custle/core-gm/grpc/internal/channelz"
    69  	"gitee.com/ks-custle/core-gm/grpc/keepalive"
    70  	"gitee.com/ks-custle/core-gm/grpc/metadata"
    71  	"gitee.com/ks-custle/core-gm/grpc/test/bufconn"
    72  
    73  	testgrpc "gitee.com/ks-custle/core-gm/grpc/interop/grpc_testing"
    74  	testpb "gitee.com/ks-custle/core-gm/grpc/interop/grpc_testing"
    75  )
    76  
    77  var (
    78  	workloads = flags.StringWithAllowedValues("workloads", workloadsAll,
    79  		fmt.Sprintf("Workloads to execute - One of: %v", strings.Join(allWorkloads, ", ")), allWorkloads)
    80  	traceMode = flags.StringWithAllowedValues("trace", toggleModeOff,
    81  		fmt.Sprintf("Trace mode - One of: %v", strings.Join(allToggleModes, ", ")), allToggleModes)
    82  	preloaderMode = flags.StringWithAllowedValues("preloader", toggleModeOff,
    83  		fmt.Sprintf("Preloader mode - One of: %v", strings.Join(allToggleModes, ", ")), allToggleModes)
    84  	channelzOn = flags.StringWithAllowedValues("channelz", toggleModeOff,
    85  		fmt.Sprintf("Channelz mode - One of: %v", strings.Join(allToggleModes, ", ")), allToggleModes)
    86  	compressorMode = flags.StringWithAllowedValues("compression", compModeOff,
    87  		fmt.Sprintf("Compression mode - One of: %v", strings.Join(allCompModes, ", ")), allCompModes)
    88  	networkMode = flags.StringWithAllowedValues("networkMode", networkModeNone,
    89  		"Network mode includes LAN, WAN, Local and Longhaul", allNetworkModes)
    90  	readLatency           = flags.DurationSlice("latency", defaultReadLatency, "Simulated one-way network latency - may be a comma-separated list")
    91  	readKbps              = flags.IntSlice("kbps", defaultReadKbps, "Simulated network throughput (in kbps) - may be a comma-separated list")
    92  	readMTU               = flags.IntSlice("mtu", defaultReadMTU, "Simulated network MTU (Maximum Transmission Unit) - may be a comma-separated list")
    93  	maxConcurrentCalls    = flags.IntSlice("maxConcurrentCalls", defaultMaxConcurrentCalls, "Number of concurrent RPCs during benchmarks")
    94  	readReqSizeBytes      = flags.IntSlice("reqSizeBytes", nil, "Request size in bytes - may be a comma-separated list")
    95  	readRespSizeBytes     = flags.IntSlice("respSizeBytes", nil, "Response size in bytes - may be a comma-separated list")
    96  	reqPayloadCurveFiles  = flags.StringSlice("reqPayloadCurveFiles", nil, "comma-separated list of CSV files describing the shape a random distribution of request payload sizes")
    97  	respPayloadCurveFiles = flags.StringSlice("respPayloadCurveFiles", nil, "comma-separated list of CSV files describing the shape a random distribution of response payload sizes")
    98  	benchTime             = flag.Duration("benchtime", time.Second, "Configures the amount of time to run each benchmark")
    99  	memProfile            = flag.String("memProfile", "", "Enables memory profiling output to the filename provided.")
   100  	memProfileRate        = flag.Int("memProfileRate", 512*1024, "Configures the memory profiling rate. \n"+
   101  		"memProfile should be set before setting profile rate. To include every allocated block in the profile, "+
   102  		"set MemProfileRate to 1. To turn off profiling entirely, set MemProfileRate to 0. 512 * 1024 by default.")
   103  	cpuProfile          = flag.String("cpuProfile", "", "Enables CPU profiling output to the filename provided")
   104  	benchmarkResultFile = flag.String("resultFile", "", "Save the benchmark result into a binary file")
   105  	useBufconn          = flag.Bool("bufconn", false, "Use in-memory connection instead of system network I/O")
   106  	enableKeepalive     = flag.Bool("enable_keepalive", false, "Enable client keepalive. \n"+
   107  		"Keepalive.Time is set to 10s, Keepalive.Timeout is set to 1s, Keepalive.PermitWithoutStream is set to true.")
   108  
   109  	logger = grpclog.Component("benchmark")
   110  )
   111  
   112  const (
   113  	workloadsUnary         = "unary"
   114  	workloadsStreaming     = "streaming"
   115  	workloadsUnconstrained = "unconstrained"
   116  	workloadsAll           = "all"
   117  	// Compression modes.
   118  	compModeOff  = "off"
   119  	compModeGzip = "gzip"
   120  	compModeNop  = "nop"
   121  	compModeAll  = "all"
   122  	// Toggle modes.
   123  	toggleModeOff  = "off"
   124  	toggleModeOn   = "on"
   125  	toggleModeBoth = "both"
   126  	// Network modes.
   127  	networkModeNone  = "none"
   128  	networkModeLocal = "Local"
   129  	networkModeLAN   = "LAN"
   130  	networkModeWAN   = "WAN"
   131  	networkLongHaul  = "Longhaul"
   132  
   133  	numStatsBuckets = 10
   134  	warmupCallCount = 10
   135  	warmuptime      = time.Second
   136  )
   137  
   138  var (
   139  	allWorkloads              = []string{workloadsUnary, workloadsStreaming, workloadsUnconstrained, workloadsAll}
   140  	allCompModes              = []string{compModeOff, compModeGzip, compModeNop, compModeAll}
   141  	allToggleModes            = []string{toggleModeOff, toggleModeOn, toggleModeBoth}
   142  	allNetworkModes           = []string{networkModeNone, networkModeLocal, networkModeLAN, networkModeWAN, networkLongHaul}
   143  	defaultReadLatency        = []time.Duration{0, 40 * time.Millisecond} // if non-positive, no delay.
   144  	defaultReadKbps           = []int{0, 10240}                           // if non-positive, infinite
   145  	defaultReadMTU            = []int{0}                                  // if non-positive, infinite
   146  	defaultMaxConcurrentCalls = []int{1, 8, 64, 512}
   147  	defaultReqSizeBytes       = []int{1, 1024, 1024 * 1024}
   148  	defaultRespSizeBytes      = []int{1, 1024, 1024 * 1024}
   149  	networks                  = map[string]latency.Network{
   150  		networkModeLocal: latency.Local,
   151  		networkModeLAN:   latency.LAN,
   152  		networkModeWAN:   latency.WAN,
   153  		networkLongHaul:  latency.Longhaul,
   154  	}
   155  	keepaliveTime    = 10 * time.Second
   156  	keepaliveTimeout = 1 * time.Second
   157  	// This is 0.8*keepaliveTime to prevent connection issues because of server
   158  	// keepalive enforcement.
   159  	keepaliveMinTime = 8 * time.Second
   160  )
   161  
   162  // runModes indicates the workloads to run. This is initialized with a call to
   163  // `runModesFromWorkloads`, passing the workloads flag set by the user.
   164  type runModes struct {
   165  	unary, streaming, unconstrained bool
   166  }
   167  
   168  // runModesFromWorkloads determines the runModes based on the value of
   169  // workloads flag set by the user.
   170  func runModesFromWorkloads(workload string) runModes {
   171  	r := runModes{}
   172  	switch workload {
   173  	case workloadsUnary:
   174  		r.unary = true
   175  	case workloadsStreaming:
   176  		r.streaming = true
   177  	case workloadsUnconstrained:
   178  		r.unconstrained = true
   179  	case workloadsAll:
   180  		r.unary = true
   181  		r.streaming = true
   182  		r.unconstrained = true
   183  	default:
   184  		log.Fatalf("Unknown workloads setting: %v (want one of: %v)",
   185  			workloads, strings.Join(allWorkloads, ", "))
   186  	}
   187  	return r
   188  }
   189  
   190  type startFunc func(mode string, bf stats.Features)
   191  type stopFunc func(count uint64)
   192  type ucStopFunc func(req uint64, resp uint64)
   193  type rpcCallFunc func(pos int)
   194  type rpcSendFunc func(pos int)
   195  type rpcRecvFunc func(pos int)
   196  type rpcCleanupFunc func()
   197  
   198  func unaryBenchmark(start startFunc, stop stopFunc, bf stats.Features, s *stats.Stats) {
   199  	caller, cleanup := makeFuncUnary(bf)
   200  	defer cleanup()
   201  	runBenchmark(caller, start, stop, bf, s, workloadsUnary)
   202  }
   203  
   204  func streamBenchmark(start startFunc, stop stopFunc, bf stats.Features, s *stats.Stats) {
   205  	caller, cleanup := makeFuncStream(bf)
   206  	defer cleanup()
   207  	runBenchmark(caller, start, stop, bf, s, workloadsStreaming)
   208  }
   209  
   210  func unconstrainedStreamBenchmark(start startFunc, stop ucStopFunc, bf stats.Features) {
   211  	var sender rpcSendFunc
   212  	var recver rpcRecvFunc
   213  	var cleanup rpcCleanupFunc
   214  	if bf.EnablePreloader {
   215  		sender, recver, cleanup = makeFuncUnconstrainedStreamPreloaded(bf)
   216  	} else {
   217  		sender, recver, cleanup = makeFuncUnconstrainedStream(bf)
   218  	}
   219  	defer cleanup()
   220  
   221  	var req, resp uint64
   222  	go func() {
   223  		// Resets the counters once warmed up
   224  		<-time.NewTimer(warmuptime).C
   225  		atomic.StoreUint64(&req, 0)
   226  		atomic.StoreUint64(&resp, 0)
   227  		start(workloadsUnconstrained, bf)
   228  	}()
   229  
   230  	bmEnd := time.Now().Add(bf.BenchTime + warmuptime)
   231  	var wg sync.WaitGroup
   232  	wg.Add(2 * bf.MaxConcurrentCalls)
   233  	for i := 0; i < bf.MaxConcurrentCalls; i++ {
   234  		go func(pos int) {
   235  			defer wg.Done()
   236  			for {
   237  				t := time.Now()
   238  				if t.After(bmEnd) {
   239  					return
   240  				}
   241  				sender(pos)
   242  				atomic.AddUint64(&req, 1)
   243  			}
   244  		}(i)
   245  		go func(pos int) {
   246  			defer wg.Done()
   247  			for {
   248  				t := time.Now()
   249  				if t.After(bmEnd) {
   250  					return
   251  				}
   252  				recver(pos)
   253  				atomic.AddUint64(&resp, 1)
   254  			}
   255  		}(i)
   256  	}
   257  	wg.Wait()
   258  	stop(req, resp)
   259  }
   260  
   261  // makeClient returns a gRPC client for the grpc.testing.BenchmarkService
   262  // service. The client is configured using the different options in the passed
   263  // 'bf'. Also returns a cleanup function to close the client and release
   264  // resources.
   265  func makeClient(bf stats.Features) (testgrpc.BenchmarkServiceClient, func()) {
   266  	nw := &latency.Network{Kbps: bf.Kbps, Latency: bf.Latency, MTU: bf.MTU}
   267  	opts := []grpc.DialOption{}
   268  	sopts := []grpc.ServerOption{}
   269  	if bf.ModeCompressor == compModeNop {
   270  		sopts = append(sopts,
   271  			grpc.RPCCompressor(nopCompressor{}),
   272  			grpc.RPCDecompressor(nopDecompressor{}),
   273  		)
   274  		opts = append(opts,
   275  			grpc.WithCompressor(nopCompressor{}),
   276  			grpc.WithDecompressor(nopDecompressor{}),
   277  		)
   278  	}
   279  	if bf.ModeCompressor == compModeGzip {
   280  		sopts = append(sopts,
   281  			grpc.RPCCompressor(grpc.NewGZIPCompressor()),
   282  			grpc.RPCDecompressor(grpc.NewGZIPDecompressor()),
   283  		)
   284  		opts = append(opts,
   285  			grpc.WithCompressor(grpc.NewGZIPCompressor()),
   286  			grpc.WithDecompressor(grpc.NewGZIPDecompressor()),
   287  		)
   288  	}
   289  	if bf.EnableKeepalive {
   290  		sopts = append(sopts,
   291  			grpc.KeepaliveParams(keepalive.ServerParameters{
   292  				Time:    keepaliveTime,
   293  				Timeout: keepaliveTimeout,
   294  			}),
   295  			grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
   296  				MinTime:             keepaliveMinTime,
   297  				PermitWithoutStream: true,
   298  			}),
   299  		)
   300  		opts = append(opts,
   301  			grpc.WithKeepaliveParams(keepalive.ClientParameters{
   302  				Time:                keepaliveTime,
   303  				Timeout:             keepaliveTimeout,
   304  				PermitWithoutStream: true,
   305  			}),
   306  		)
   307  	}
   308  	sopts = append(sopts, grpc.MaxConcurrentStreams(uint32(bf.MaxConcurrentCalls+1)))
   309  	opts = append(opts, grpc.WithInsecure())
   310  
   311  	var lis net.Listener
   312  	if bf.UseBufConn {
   313  		bcLis := bufconn.Listen(256 * 1024)
   314  		lis = bcLis
   315  		opts = append(opts, grpc.WithContextDialer(func(ctx context.Context, address string) (net.Conn, error) {
   316  			return nw.ContextDialer(func(context.Context, string, string) (net.Conn, error) {
   317  				return bcLis.Dial()
   318  			})(ctx, "", "")
   319  		}))
   320  	} else {
   321  		var err error
   322  		lis, err = net.Listen("tcp", "localhost:0")
   323  		if err != nil {
   324  			logger.Fatalf("Failed to listen: %v", err)
   325  		}
   326  		opts = append(opts, grpc.WithContextDialer(func(ctx context.Context, address string) (net.Conn, error) {
   327  			return nw.ContextDialer((&net.Dialer{}).DialContext)(ctx, "tcp", lis.Addr().String())
   328  		}))
   329  	}
   330  	lis = nw.Listener(lis)
   331  	stopper := bm.StartServer(bm.ServerInfo{Type: "protobuf", Listener: lis}, sopts...)
   332  	conn := bm.NewClientConn("" /* target not used */, opts...)
   333  	return testgrpc.NewBenchmarkServiceClient(conn), func() {
   334  		conn.Close()
   335  		stopper()
   336  	}
   337  }
   338  
   339  func makeFuncUnary(bf stats.Features) (rpcCallFunc, rpcCleanupFunc) {
   340  	tc, cleanup := makeClient(bf)
   341  	return func(int) {
   342  		reqSizeBytes := bf.ReqSizeBytes
   343  		respSizeBytes := bf.RespSizeBytes
   344  		if bf.ReqPayloadCurve != nil {
   345  			reqSizeBytes = bf.ReqPayloadCurve.ChooseRandom()
   346  		}
   347  		if bf.RespPayloadCurve != nil {
   348  			respSizeBytes = bf.RespPayloadCurve.ChooseRandom()
   349  		}
   350  		unaryCaller(tc, reqSizeBytes, respSizeBytes)
   351  	}, cleanup
   352  }
   353  
   354  func makeFuncStream(bf stats.Features) (rpcCallFunc, rpcCleanupFunc) {
   355  	tc, cleanup := makeClient(bf)
   356  
   357  	streams := make([]testgrpc.BenchmarkService_StreamingCallClient, bf.MaxConcurrentCalls)
   358  	for i := 0; i < bf.MaxConcurrentCalls; i++ {
   359  		stream, err := tc.StreamingCall(context.Background())
   360  		if err != nil {
   361  			logger.Fatalf("%v.StreamingCall(_) = _, %v", tc, err)
   362  		}
   363  		streams[i] = stream
   364  	}
   365  
   366  	return func(pos int) {
   367  		reqSizeBytes := bf.ReqSizeBytes
   368  		respSizeBytes := bf.RespSizeBytes
   369  		if bf.ReqPayloadCurve != nil {
   370  			reqSizeBytes = bf.ReqPayloadCurve.ChooseRandom()
   371  		}
   372  		if bf.RespPayloadCurve != nil {
   373  			respSizeBytes = bf.RespPayloadCurve.ChooseRandom()
   374  		}
   375  		streamCaller(streams[pos], reqSizeBytes, respSizeBytes)
   376  	}, cleanup
   377  }
   378  
   379  func makeFuncUnconstrainedStreamPreloaded(bf stats.Features) (rpcSendFunc, rpcRecvFunc, rpcCleanupFunc) {
   380  	streams, req, cleanup := setupUnconstrainedStream(bf)
   381  
   382  	preparedMsg := make([]*grpc.PreparedMsg, len(streams))
   383  	for i, stream := range streams {
   384  		preparedMsg[i] = &grpc.PreparedMsg{}
   385  		err := preparedMsg[i].Encode(stream, req)
   386  		if err != nil {
   387  			logger.Fatalf("%v.Encode(%v, %v) = %v", preparedMsg[i], req, stream, err)
   388  		}
   389  	}
   390  
   391  	return func(pos int) {
   392  			streams[pos].SendMsg(preparedMsg[pos])
   393  		}, func(pos int) {
   394  			streams[pos].Recv()
   395  		}, cleanup
   396  }
   397  
   398  func makeFuncUnconstrainedStream(bf stats.Features) (rpcSendFunc, rpcRecvFunc, rpcCleanupFunc) {
   399  	streams, req, cleanup := setupUnconstrainedStream(bf)
   400  
   401  	return func(pos int) {
   402  			streams[pos].Send(req)
   403  		}, func(pos int) {
   404  			streams[pos].Recv()
   405  		}, cleanup
   406  }
   407  
   408  func setupUnconstrainedStream(bf stats.Features) ([]testgrpc.BenchmarkService_StreamingCallClient, *testpb.SimpleRequest, rpcCleanupFunc) {
   409  	tc, cleanup := makeClient(bf)
   410  
   411  	streams := make([]testgrpc.BenchmarkService_StreamingCallClient, bf.MaxConcurrentCalls)
   412  	md := metadata.Pairs(benchmark.UnconstrainedStreamingHeader, "1")
   413  	ctx := metadata.NewOutgoingContext(context.Background(), md)
   414  	for i := 0; i < bf.MaxConcurrentCalls; i++ {
   415  		stream, err := tc.StreamingCall(ctx)
   416  		if err != nil {
   417  			logger.Fatalf("%v.StreamingCall(_) = _, %v", tc, err)
   418  		}
   419  		streams[i] = stream
   420  	}
   421  
   422  	pl := bm.NewPayload(testpb.PayloadType_COMPRESSABLE, bf.ReqSizeBytes)
   423  	req := &testpb.SimpleRequest{
   424  		ResponseType: pl.Type,
   425  		ResponseSize: int32(bf.RespSizeBytes),
   426  		Payload:      pl,
   427  	}
   428  
   429  	return streams, req, cleanup
   430  }
   431  
   432  // Makes a UnaryCall gRPC request using the given BenchmarkServiceClient and
   433  // request and response sizes.
   434  func unaryCaller(client testgrpc.BenchmarkServiceClient, reqSize, respSize int) {
   435  	if err := bm.DoUnaryCall(client, reqSize, respSize); err != nil {
   436  		logger.Fatalf("DoUnaryCall failed: %v", err)
   437  	}
   438  }
   439  
   440  func streamCaller(stream testgrpc.BenchmarkService_StreamingCallClient, reqSize, respSize int) {
   441  	if err := bm.DoStreamingRoundTrip(stream, reqSize, respSize); err != nil {
   442  		logger.Fatalf("DoStreamingRoundTrip failed: %v", err)
   443  	}
   444  }
   445  
   446  func runBenchmark(caller rpcCallFunc, start startFunc, stop stopFunc, bf stats.Features, s *stats.Stats, mode string) {
   447  	// Warm up connection.
   448  	for i := 0; i < warmupCallCount; i++ {
   449  		caller(0)
   450  	}
   451  
   452  	// Run benchmark.
   453  	start(mode, bf)
   454  	var wg sync.WaitGroup
   455  	wg.Add(bf.MaxConcurrentCalls)
   456  	bmEnd := time.Now().Add(bf.BenchTime)
   457  	var count uint64
   458  	for i := 0; i < bf.MaxConcurrentCalls; i++ {
   459  		go func(pos int) {
   460  			defer wg.Done()
   461  			for {
   462  				t := time.Now()
   463  				if t.After(bmEnd) {
   464  					return
   465  				}
   466  				start := time.Now()
   467  				caller(pos)
   468  				elapse := time.Since(start)
   469  				atomic.AddUint64(&count, 1)
   470  				s.AddDuration(elapse)
   471  			}
   472  		}(i)
   473  	}
   474  	wg.Wait()
   475  	stop(count)
   476  }
   477  
   478  // benchOpts represents all configurable options available while running this
   479  // benchmark. This is built from the values passed as flags.
   480  type benchOpts struct {
   481  	rModes              runModes
   482  	benchTime           time.Duration
   483  	memProfileRate      int
   484  	memProfile          string
   485  	cpuProfile          string
   486  	networkMode         string
   487  	benchmarkResultFile string
   488  	useBufconn          bool
   489  	enableKeepalive     bool
   490  	features            *featureOpts
   491  }
   492  
   493  // featureOpts represents options which can have multiple values. The user
   494  // usually provides a comma-separated list of options for each of these
   495  // features through command line flags. We generate all possible combinations
   496  // for the provided values and run the benchmarks for each combination.
   497  type featureOpts struct {
   498  	enableTrace        []bool
   499  	readLatencies      []time.Duration
   500  	readKbps           []int
   501  	readMTU            []int
   502  	maxConcurrentCalls []int
   503  	reqSizeBytes       []int
   504  	respSizeBytes      []int
   505  	reqPayloadCurves   []*stats.PayloadCurve
   506  	respPayloadCurves  []*stats.PayloadCurve
   507  	compModes          []string
   508  	enableChannelz     []bool
   509  	enablePreloader    []bool
   510  }
   511  
   512  // makeFeaturesNum returns a slice of ints of size 'maxFeatureIndex' where each
   513  // element of the slice (indexed by 'featuresIndex' enum) contains the number
   514  // of features to be exercised by the benchmark code.
   515  // For example: Index 0 of the returned slice contains the number of values for
   516  // enableTrace feature, while index 1 contains the number of value of
   517  // readLatencies feature and so on.
   518  func makeFeaturesNum(b *benchOpts) []int {
   519  	featuresNum := make([]int, stats.MaxFeatureIndex)
   520  	for i := 0; i < len(featuresNum); i++ {
   521  		switch stats.FeatureIndex(i) {
   522  		case stats.EnableTraceIndex:
   523  			featuresNum[i] = len(b.features.enableTrace)
   524  		case stats.ReadLatenciesIndex:
   525  			featuresNum[i] = len(b.features.readLatencies)
   526  		case stats.ReadKbpsIndex:
   527  			featuresNum[i] = len(b.features.readKbps)
   528  		case stats.ReadMTUIndex:
   529  			featuresNum[i] = len(b.features.readMTU)
   530  		case stats.MaxConcurrentCallsIndex:
   531  			featuresNum[i] = len(b.features.maxConcurrentCalls)
   532  		case stats.ReqSizeBytesIndex:
   533  			featuresNum[i] = len(b.features.reqSizeBytes)
   534  		case stats.RespSizeBytesIndex:
   535  			featuresNum[i] = len(b.features.respSizeBytes)
   536  		case stats.ReqPayloadCurveIndex:
   537  			featuresNum[i] = len(b.features.reqPayloadCurves)
   538  		case stats.RespPayloadCurveIndex:
   539  			featuresNum[i] = len(b.features.respPayloadCurves)
   540  		case stats.CompModesIndex:
   541  			featuresNum[i] = len(b.features.compModes)
   542  		case stats.EnableChannelzIndex:
   543  			featuresNum[i] = len(b.features.enableChannelz)
   544  		case stats.EnablePreloaderIndex:
   545  			featuresNum[i] = len(b.features.enablePreloader)
   546  		default:
   547  			log.Fatalf("Unknown feature index %v in generateFeatures. maxFeatureIndex is %v", i, stats.MaxFeatureIndex)
   548  		}
   549  	}
   550  	return featuresNum
   551  }
   552  
   553  // sharedFeatures returns a bool slice which acts as a bitmask. Each item in
   554  // the slice represents a feature, indexed by 'featureIndex' enum.  The bit is
   555  // set to 1 if the corresponding feature does not have multiple value, so is
   556  // shared amongst all benchmarks.
   557  func sharedFeatures(featuresNum []int) []bool {
   558  	result := make([]bool, len(featuresNum))
   559  	for i, num := range featuresNum {
   560  		if num <= 1 {
   561  			result[i] = true
   562  		}
   563  	}
   564  	return result
   565  }
   566  
   567  // generateFeatures generates all combinations of the provided feature options.
   568  // While all the feature options are stored in the benchOpts struct, the input
   569  // parameter 'featuresNum' is a slice indexed by 'featureIndex' enum containing
   570  // the number of values for each feature.
   571  // For example, let's say the user sets -workloads=all and
   572  // -maxConcurrentCalls=1,100, this would end up with the following
   573  // combinations:
   574  // [workloads: unary, maxConcurrentCalls=1]
   575  // [workloads: unary, maxConcurrentCalls=1]
   576  // [workloads: streaming, maxConcurrentCalls=100]
   577  // [workloads: streaming, maxConcurrentCalls=100]
   578  // [workloads: unconstrained, maxConcurrentCalls=1]
   579  // [workloads: unconstrained, maxConcurrentCalls=100]
   580  func (b *benchOpts) generateFeatures(featuresNum []int) []stats.Features {
   581  	// curPos and initialPos are two slices where each value acts as an index
   582  	// into the appropriate feature slice maintained in benchOpts.features. This
   583  	// loop generates all possible combinations of features by changing one value
   584  	// at a time, and once curPos becomes equal to initialPos, we have explored
   585  	// all options.
   586  	var result []stats.Features
   587  	var curPos []int
   588  	initialPos := make([]int, stats.MaxFeatureIndex)
   589  	for !reflect.DeepEqual(initialPos, curPos) {
   590  		if curPos == nil {
   591  			curPos = make([]int, stats.MaxFeatureIndex)
   592  		}
   593  		f := stats.Features{
   594  			// These features stay the same for each iteration.
   595  			NetworkMode:     b.networkMode,
   596  			UseBufConn:      b.useBufconn,
   597  			EnableKeepalive: b.enableKeepalive,
   598  			BenchTime:       b.benchTime,
   599  			// These features can potentially change for each iteration.
   600  			EnableTrace:        b.features.enableTrace[curPos[stats.EnableTraceIndex]],
   601  			Latency:            b.features.readLatencies[curPos[stats.ReadLatenciesIndex]],
   602  			Kbps:               b.features.readKbps[curPos[stats.ReadKbpsIndex]],
   603  			MTU:                b.features.readMTU[curPos[stats.ReadMTUIndex]],
   604  			MaxConcurrentCalls: b.features.maxConcurrentCalls[curPos[stats.MaxConcurrentCallsIndex]],
   605  			ModeCompressor:     b.features.compModes[curPos[stats.CompModesIndex]],
   606  			EnableChannelz:     b.features.enableChannelz[curPos[stats.EnableChannelzIndex]],
   607  			EnablePreloader:    b.features.enablePreloader[curPos[stats.EnablePreloaderIndex]],
   608  		}
   609  		if len(b.features.reqPayloadCurves) == 0 {
   610  			f.ReqSizeBytes = b.features.reqSizeBytes[curPos[stats.ReqSizeBytesIndex]]
   611  		} else {
   612  			f.ReqPayloadCurve = b.features.reqPayloadCurves[curPos[stats.ReqPayloadCurveIndex]]
   613  		}
   614  		if len(b.features.respPayloadCurves) == 0 {
   615  			f.RespSizeBytes = b.features.respSizeBytes[curPos[stats.RespSizeBytesIndex]]
   616  		} else {
   617  			f.RespPayloadCurve = b.features.respPayloadCurves[curPos[stats.RespPayloadCurveIndex]]
   618  		}
   619  		result = append(result, f)
   620  		addOne(curPos, featuresNum)
   621  	}
   622  	return result
   623  }
   624  
   625  // addOne mutates the input slice 'features' by changing one feature, thus
   626  // arriving at the next combination of feature values. 'featuresMaxPosition'
   627  // provides the numbers of allowed values for each feature, indexed by
   628  // 'featureIndex' enum.
   629  func addOne(features []int, featuresMaxPosition []int) {
   630  	for i := len(features) - 1; i >= 0; i-- {
   631  		if featuresMaxPosition[i] == 0 {
   632  			continue
   633  		}
   634  		features[i] = (features[i] + 1)
   635  		if features[i]/featuresMaxPosition[i] == 0 {
   636  			break
   637  		}
   638  		features[i] = features[i] % featuresMaxPosition[i]
   639  	}
   640  }
   641  
   642  // processFlags reads the command line flags and builds benchOpts. Specifying
   643  // invalid values for certain flags will cause flag.Parse() to fail, and the
   644  // program to terminate.
   645  // This *SHOULD* be the only place where the flags are accessed. All other
   646  // parts of the benchmark code should rely on the returned benchOpts.
   647  func processFlags() *benchOpts {
   648  	flag.Parse()
   649  	if flag.NArg() != 0 {
   650  		log.Fatal("Error: unparsed arguments: ", flag.Args())
   651  	}
   652  
   653  	opts := &benchOpts{
   654  		rModes:              runModesFromWorkloads(*workloads),
   655  		benchTime:           *benchTime,
   656  		memProfileRate:      *memProfileRate,
   657  		memProfile:          *memProfile,
   658  		cpuProfile:          *cpuProfile,
   659  		networkMode:         *networkMode,
   660  		benchmarkResultFile: *benchmarkResultFile,
   661  		useBufconn:          *useBufconn,
   662  		enableKeepalive:     *enableKeepalive,
   663  		features: &featureOpts{
   664  			enableTrace:        setToggleMode(*traceMode),
   665  			readLatencies:      append([]time.Duration(nil), *readLatency...),
   666  			readKbps:           append([]int(nil), *readKbps...),
   667  			readMTU:            append([]int(nil), *readMTU...),
   668  			maxConcurrentCalls: append([]int(nil), *maxConcurrentCalls...),
   669  			reqSizeBytes:       append([]int(nil), *readReqSizeBytes...),
   670  			respSizeBytes:      append([]int(nil), *readRespSizeBytes...),
   671  			compModes:          setCompressorMode(*compressorMode),
   672  			enableChannelz:     setToggleMode(*channelzOn),
   673  			enablePreloader:    setToggleMode(*preloaderMode),
   674  		},
   675  	}
   676  
   677  	if len(*reqPayloadCurveFiles) == 0 {
   678  		if len(opts.features.reqSizeBytes) == 0 {
   679  			opts.features.reqSizeBytes = defaultReqSizeBytes
   680  		}
   681  	} else {
   682  		if len(opts.features.reqSizeBytes) != 0 {
   683  			log.Fatalf("you may not specify -reqPayloadCurveFiles and -reqSizeBytes at the same time")
   684  		}
   685  		for _, file := range *reqPayloadCurveFiles {
   686  			pc, err := stats.NewPayloadCurve(file)
   687  			if err != nil {
   688  				log.Fatalf("cannot load payload curve file %s: %v", file, err)
   689  			}
   690  			opts.features.reqPayloadCurves = append(opts.features.reqPayloadCurves, pc)
   691  		}
   692  		opts.features.reqSizeBytes = nil
   693  	}
   694  	if len(*respPayloadCurveFiles) == 0 {
   695  		if len(opts.features.respSizeBytes) == 0 {
   696  			opts.features.respSizeBytes = defaultRespSizeBytes
   697  		}
   698  	} else {
   699  		if len(opts.features.respSizeBytes) != 0 {
   700  			log.Fatalf("you may not specify -respPayloadCurveFiles and -respSizeBytes at the same time")
   701  		}
   702  		for _, file := range *respPayloadCurveFiles {
   703  			pc, err := stats.NewPayloadCurve(file)
   704  			if err != nil {
   705  				log.Fatalf("cannot load payload curve file %s: %v", file, err)
   706  			}
   707  			opts.features.respPayloadCurves = append(opts.features.respPayloadCurves, pc)
   708  		}
   709  		opts.features.respSizeBytes = nil
   710  	}
   711  
   712  	// Re-write latency, kpbs and mtu if network mode is set.
   713  	if network, ok := networks[opts.networkMode]; ok {
   714  		opts.features.readLatencies = []time.Duration{network.Latency}
   715  		opts.features.readKbps = []int{network.Kbps}
   716  		opts.features.readMTU = []int{network.MTU}
   717  	}
   718  	return opts
   719  }
   720  
   721  func setToggleMode(val string) []bool {
   722  	switch val {
   723  	case toggleModeOn:
   724  		return []bool{true}
   725  	case toggleModeOff:
   726  		return []bool{false}
   727  	case toggleModeBoth:
   728  		return []bool{false, true}
   729  	default:
   730  		// This should never happen because a wrong value passed to this flag would
   731  		// be caught during flag.Parse().
   732  		return []bool{}
   733  	}
   734  }
   735  
   736  func setCompressorMode(val string) []string {
   737  	switch val {
   738  	case compModeNop, compModeGzip, compModeOff:
   739  		return []string{val}
   740  	case compModeAll:
   741  		return []string{compModeNop, compModeGzip, compModeOff}
   742  	default:
   743  		// This should never happen because a wrong value passed to this flag would
   744  		// be caught during flag.Parse().
   745  		return []string{}
   746  	}
   747  }
   748  
   749  func main() {
   750  	opts := processFlags()
   751  	before(opts)
   752  
   753  	s := stats.NewStats(numStatsBuckets)
   754  	featuresNum := makeFeaturesNum(opts)
   755  	sf := sharedFeatures(featuresNum)
   756  
   757  	var (
   758  		start  = func(mode string, bf stats.Features) { s.StartRun(mode, bf, sf) }
   759  		stop   = func(count uint64) { s.EndRun(count) }
   760  		ucStop = func(req uint64, resp uint64) { s.EndUnconstrainedRun(req, resp) }
   761  	)
   762  
   763  	for _, bf := range opts.generateFeatures(featuresNum) {
   764  		grpc.EnableTracing = bf.EnableTrace
   765  		if bf.EnableChannelz {
   766  			channelz.TurnOn()
   767  		}
   768  		if opts.rModes.unary {
   769  			unaryBenchmark(start, stop, bf, s)
   770  		}
   771  		if opts.rModes.streaming {
   772  			streamBenchmark(start, stop, bf, s)
   773  		}
   774  		if opts.rModes.unconstrained {
   775  			unconstrainedStreamBenchmark(start, ucStop, bf)
   776  		}
   777  	}
   778  	after(opts, s.GetResults())
   779  }
   780  
   781  func before(opts *benchOpts) {
   782  	if opts.memProfile != "" {
   783  		runtime.MemProfileRate = opts.memProfileRate
   784  	}
   785  	if opts.cpuProfile != "" {
   786  		f, err := os.Create(opts.cpuProfile)
   787  		if err != nil {
   788  			fmt.Fprintf(os.Stderr, "testing: %s\n", err)
   789  			return
   790  		}
   791  		if err := pprof.StartCPUProfile(f); err != nil {
   792  			fmt.Fprintf(os.Stderr, "testing: can't start cpu profile: %s\n", err)
   793  			f.Close()
   794  			return
   795  		}
   796  	}
   797  }
   798  
   799  func after(opts *benchOpts, data []stats.BenchResults) {
   800  	if opts.cpuProfile != "" {
   801  		pprof.StopCPUProfile() // flushes profile to disk
   802  	}
   803  	if opts.memProfile != "" {
   804  		f, err := os.Create(opts.memProfile)
   805  		if err != nil {
   806  			fmt.Fprintf(os.Stderr, "testing: %s\n", err)
   807  			os.Exit(2)
   808  		}
   809  		runtime.GC() // materialize all statistics
   810  		if err = pprof.WriteHeapProfile(f); err != nil {
   811  			fmt.Fprintf(os.Stderr, "testing: can't write heap profile %s: %s\n", opts.memProfile, err)
   812  			os.Exit(2)
   813  		}
   814  		f.Close()
   815  	}
   816  	if opts.benchmarkResultFile != "" {
   817  		f, err := os.Create(opts.benchmarkResultFile)
   818  		if err != nil {
   819  			log.Fatalf("testing: can't write benchmark result %s: %s\n", opts.benchmarkResultFile, err)
   820  		}
   821  		dataEncoder := gob.NewEncoder(f)
   822  		dataEncoder.Encode(data)
   823  		f.Close()
   824  	}
   825  }
   826  
   827  // nopCompressor is a compressor that just copies data.
   828  type nopCompressor struct{}
   829  
   830  func (nopCompressor) Do(w io.Writer, p []byte) error {
   831  	n, err := w.Write(p)
   832  	if err != nil {
   833  		return err
   834  	}
   835  	if n != len(p) {
   836  		return fmt.Errorf("nopCompressor.Write: wrote %v bytes; want %v", n, len(p))
   837  	}
   838  	return nil
   839  }
   840  
   841  func (nopCompressor) Type() string { return compModeNop }
   842  
   843  // nopDecompressor is a decompressor that just copies data.
   844  type nopDecompressor struct{}
   845  
   846  func (nopDecompressor) Do(r io.Reader) ([]byte, error) { return ioutil.ReadAll(r) }
   847  func (nopDecompressor) Type() string                   { return compModeNop }