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 }