Repository: grpc/grpc-go
File: benchmark/worker/benchmark_client.go (Go)

Code before:
	return bc, nil
}

func (bc *benchmarkClient) unaryLoop(ctx context.Context, conns []*grpc.ClientConn, rpcCountPerConn int, reqSize int, respSize int, poissonLambda *float64) {
	for ic, conn := range conns {
		client := testgrpc.NewBenchmarkServiceClient(conn)
		// For each connection, create rpcCountPerConn goroutines to do rpc.
		for j := range rpcCountPerConn {
			// Create histogram for each goroutine.
			idx := ic*rpcCountPerConn + j
			bc.lockingHistograms[idx].histogram = stats.NewHistogram(bc.histogramOptions)
			// Start goroutine on the created mutex and histogram.
			go func(idx int) {
				// TODO: do warm up if necessary.
				// Now relying on worker client to reserve time to do warm up.
				// The worker client needs to wait for some time after client is created,
				// before starting benchmark.
				if poissonLambda == nil { // Closed loop.
					for {
						start := time.Now()
						if err := benchmark.DoUnaryCall(ctx, client, reqSize, respSize); err != nil {
							if status.Code(err) == codes.Canceled {
								return
							}
							continue
						}
						elapse := time.Since(start)
						bc.lockingHistograms[idx].add(int64(elapse))
					}
				} else { // Open loop.
					timeBetweenRPCs := time.Duration((rand.ExpFloat64() / *poissonLambda) * float64(time.Second))
					time.AfterFunc(timeBetweenRPCs, func() {
						bc.poissonUnary(ctx, client, idx, reqSize, respSize, *poissonLambda)
					})
				}
			}(idx)
		}
	}
}

func (bc *benchmarkClient) streamingLoop(ctx context.Context, conns []*grpc.ClientConn, rpcCountPerConn int, reqSize int, respSize int, payloadType string, poissonLambda *float64) {
	var doRPC func(testgrpc.BenchmarkService_StreamingCallClient, int, int) error
	if payloadType == "bytebuf" {
		doRPC = benchmark.DoByteBufStreamingRoundTrip
	} else {
		doRPC = benchmark.DoStreamingRoundTrip
	}
	for ic, conn := range conns {
		// For each connection, create rpcCountPerConn goroutines to do rpc.
		for j := 0; j < rpcCountPerConn; j++ {
			c := testgrpc.NewBenchmarkServiceClient(conn)

Review comment (performance):
Instead of checking the status code of the RPC, we should check `ctx.Err() != nil` juet before making the request. Using a cancellable context for making requests has a big performance impact. See https://github.com/grpc/grpc-go/pull/8614#issuecomment-3414787984

Code after:
	return bc, nil
}

func (bc *benchmarkClient) unaryLoop(ctx context.Context, conns []*grpc.ClientConn, rpcCountPerConn int, reqSize int, respSize int, poissonLambda *float64) {
	for ic, conn := range conns {
		client := testgrpc.NewBenchmarkServiceClient(conn)
		// For each connection, create rpcCountPerConn goroutines to do rpc.
		for j := range rpcCountPerConn {
			// Create histogram for each goroutine.
			idx := ic*rpcCountPerConn + j
			bc.lockingHistograms[idx].histogram = stats.NewHistogram(bc.histogramOptions)
			// Start goroutine on the created mutex and histogram.
			go func(idx int) {
				// TODO: do warm up if necessary.
				// Now relying on worker client to reserve time to do warm up.
				// The worker client needs to wait for some time after client is created,
				// before starting benchmark.
				if poissonLambda == nil { // Closed loop.
					for {
						if ctx.Err() != nil {
							break
						}
						start := time.Now()
						if err := benchmark.DoUnaryCall(client, reqSize, respSize); err != nil {
							continue
						}
						elapse := time.Since(start)
						bc.lockingHistograms[idx].add(int64(elapse))
					}
				} else { // Open loop.
					timeBetweenRPCs := time.Duration((rand.ExpFloat64() / *poissonLambda) * float64(time.Second))
					time.AfterFunc(timeBetweenRPCs, func() {
						bc.poissonUnary(client, idx, reqSize, respSize, *poissonLambda)
					})
				}
			}(idx)
		}
	}
}

func (bc *benchmarkClient) streamingLoop(ctx context.Context, conns []*grpc.ClientConn, rpcCountPerConn int, reqSize int, respSize int, payloadType string, poissonLambda *float64) {
	var doRPC func(testgrpc.BenchmarkService_StreamingCallClient, int, int) error
	if payloadType == "bytebuf" {
		doRPC = benchmark.DoByteBufStreamingRoundTrip
	} else {
		doRPC = benchmark.DoStreamingRoundTrip
	}
	for ic, conn := range conns {
		// For each connection, create rpcCountPerConn goroutines to do rpc.
		for j := 0; j < rpcCountPerConn; j++ {
			c := testgrpc.NewBenchmarkServiceClient(conn)