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