Skip to content

Commit 37484c7

Browse files
authored
Merge pull request #254 from bojand/stream_close_data_perf
Additional stream options
2 parents 33676b4 + ac7339a commit 37484c7

File tree

21 files changed

+3025
-617
lines changed

21 files changed

+3025
-617
lines changed

README.md

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?style=flat-square)](https://www.paypal.me/bojandj)
1414
[![Buy me a coffee](https://img.shields.io/badge/buy%20me-a%20coffee-orange.svg?style=flat-square)](https://www.buymeacoffee.com/bojand)
1515

16-
Simple [gRPC](http://grpc.io/) benchmarking and load testing tool inspired by [hey](https://github.com/rakyll/hey/) and [grpcurl](https://github.com/fullstorydev/grpcurl).
16+
[gRPC](http://grpc.io/) benchmarking and load testing tool.
1717

1818
## Documentation
1919

@@ -59,62 +59,66 @@ go build .
5959
usage: ghz [<flags>] [<host>]
6060
6161
Flags:
62-
-h, --help Show context-sensitive help (also try --help-long and --help-man).
63-
--config= Path to the JSON or TOML config file that specifies all the test run settings.
64-
--proto= The Protocol Buffer .proto file.
65-
--protoset= The compiled protoset file. Alternative to proto. -proto takes precedence.
66-
--call= A fully-qualified method name in 'package.Service/method' or 'package.Service.Method' format.
67-
-i, --import-paths= Comma separated list of proto import paths. The current working directory and the directory of the protocol buffer file are automatically added to the import list.
68-
--cacert= File containing trusted root certificates for verifying the server.
69-
--cert= File containing client certificate (public key), to present to the server. Must also provide -key option.
70-
--key= File containing client private key, to present to the server. Must also provide -cert option.
71-
--cname= Server name override when validating TLS certificate - useful for self signed certs.
72-
--skipTLS Skip TLS client verification of the server's certificate chain and host name.
73-
--insecure Use plaintext and insecure connection.
74-
--authority= Value to be used as the :authority pseudo-header. Only works if -insecure is used.
75-
--async Make requests asynchronous as soon as possible. Does not wait for request to finish before sending next one.
76-
-r, --rps=0 Requests per second (RPS) rate limit for constant load schedule. Default is no rate limit.
77-
--load-schedule="const" Specifies the load schedule. Options are const, step, or line. Default is const.
78-
--load-start=0 Specifies the RPS load start value for step or line schedules.
79-
--load-step=0 Specifies the load step value or slope value.
80-
--load-end=0 Specifies the load end value for step or line load schedules.
81-
--load-step-duration=0 Specifies the load step duration value for step load schedule.
82-
--load-max-duration=0 Specifies the max load duration value for step or line load schedule.
83-
-c, --concurrency=50 Number of request workers to run concurrently for const concurrency schedule. Default is 50.
62+
-h, --help Show context-sensitive help (also try --help-long and --help-man).
63+
--config= Path to the JSON or TOML config file that specifies all the test run settings.
64+
--proto= The Protocol Buffer .proto file.
65+
--protoset= The compiled protoset file. Alternative to proto. -proto takes precedence.
66+
--call= A fully-qualified method name in 'package.Service/method' or 'package.Service.Method' format.
67+
-i, --import-paths= Comma separated list of proto import paths. The current working directory and the directory of the protocol buffer file are automatically added to the import list.
68+
--cacert= File containing trusted root certificates for verifying the server.
69+
--cert= File containing client certificate (public key), to present to the server. Must also provide -key option.
70+
--key= File containing client private key, to present to the server. Must also provide -cert option.
71+
--cname= Server name override when validating TLS certificate - useful for self signed certs.
72+
--skipTLS Skip TLS client verification of the server's certificate chain and host name.
73+
--insecure Use plaintext and insecure connection.
74+
--authority= Value to be used as the :authority pseudo-header. Only works if -insecure is used.
75+
--async Make requests asynchronous as soon as possible. Does not wait for request to finish before sending next one.
76+
-r, --rps=0 Requests per second (RPS) rate limit for constant load schedule. Default is no rate limit.
77+
--load-schedule="const" Specifies the load schedule. Options are const, step, or line. Default is const.
78+
--load-start=0 Specifies the RPS load start value for step or line schedules.
79+
--load-step=0 Specifies the load step value or slope value.
80+
--load-end=0 Specifies the load end value for step or line load schedules.
81+
--load-step-duration=0 Specifies the load step duration value for step load schedule.
82+
--load-max-duration=0 Specifies the max load duration value for step or line load schedule.
83+
-c, --concurrency=50 Number of request workers to run concurrently for const concurrency schedule. Default is 50.
8484
--concurrency-schedule="const"
85-
Concurrency change schedule. Options are const, step, or line. Default is const.
86-
--concurrency-start=0 Concurrency start value for step and line concurrency schedules.
87-
--concurrency-end=0 Concurrency end value for step and line concurrency schedules.
88-
--concurrency-step=1 Concurrency step / slope value for step and line concurrency schedules.
85+
Concurrency change schedule. Options are const, step, or line. Default is const.
86+
--concurrency-start=0 Concurrency start value for step and line concurrency schedules.
87+
--concurrency-end=0 Concurrency end value for step and line concurrency schedules.
88+
--concurrency-step=1 Concurrency step / slope value for step and line concurrency schedules.
8989
--concurrency-step-duration=0
90-
Specifies the concurrency step duration value for step concurrency schedule.
90+
Specifies the concurrency step duration value for step concurrency schedule.
9191
--concurrency-max-duration=0
92-
Specifies the max concurrency adjustment duration value for step or line concurrency schedule.
93-
-n, --total=200 Number of requests to run. Default is 200.
94-
-t, --timeout=20s Timeout for each request. Default is 20s, use 0 for infinite.
95-
-z, --duration=0 Duration of application to send requests. When duration is reached, application stops and exits. If duration is specified, n is ignored. Examples: -z 10s -z 3m.
96-
-x, --max-duration=0 Maximum duration of application to send requests with n setting respected. If duration is reached before n requests are completed, application stops and exits. Examples: -x 10s -x 3m.
97-
--duration-stop="close" Specifies how duration stop is reported. Options are close, wait or ignore. Default is close.
98-
-d, --data= The call data as stringified JSON. If the value is '@' then the request contents are read from stdin.
99-
-D, --data-file= File path for call data JSON file. Examples: /home/user/file.json or ./file.json.
100-
-b, --binary The call data comes as serialized binary message or multiple count-prefixed messages read from stdin.
101-
-B, --binary-file= File path for the call data as serialized binary message or multiple count-prefixed messages.
102-
-m, --metadata= Request metadata as stringified JSON.
103-
-M, --metadata-file= File path for call metadata JSON file. Examples: /home/user/metadata.json or ./metadata.json.
104-
--stream-interval=0 Interval for stream requests between message sends.
105-
--reflect-metadata= Reflect metadata as stringified JSON used only for reflection request.
106-
-o, --output= Output path. If none provided stdout is used.
107-
-O, --format= Output format. One of: summary, csv, json, pretty, html, influx-summary, influx-details. Default is summary.
108-
--skipFirst=0 Skip the first X requests when doing the results tally.
109-
--connections=1 Number of connections to use. Concurrency is distributed evenly among all the connections. Default is 1.
110-
--connect-timeout=10s Connection timeout for the initial connection dial. Default is 10s.
111-
--keepalive=0 Keepalive time duration. Only used if present and above 0.
112-
--name= User specified name for the test.
113-
--tags= JSON representation of user-defined string tags.
114-
--cpus=12 Number of cpu cores to use.
115-
--debug= The path to debug log file.
116-
-e, --enable-compression Enable Gzip compression on requests.
117-
-v, --version Show application version.
92+
Specifies the max concurrency adjustment duration value for step or line concurrency schedule.
93+
-n, --total=200 Number of requests to run. Default is 200.
94+
-t, --timeout=20s Timeout for each request. Default is 20s, use 0 for infinite.
95+
-z, --duration=0 Duration of application to send requests. When duration is reached, application stops and exits. If duration is specified, n is ignored. Examples: -z 10s -z 3m.
96+
-x, --max-duration=0 Maximum duration of application to send requests with n setting respected. If duration is reached before n requests are completed, application stops and exits. Examples: -x 10s -x 3m.
97+
--duration-stop="close" Specifies how duration stop is reported. Options are close, wait or ignore. Default is close.
98+
-d, --data= The call data as stringified JSON. If the value is '@' then the request contents are read from stdin.
99+
-D, --data-file= File path for call data JSON file. Examples: /home/user/file.json or ./file.json.
100+
-b, --binary The call data comes as serialized binary message or multiple count-prefixed messages read from stdin.
101+
-B, --binary-file= File path for the call data as serialized binary message or multiple count-prefixed messages.
102+
-m, --metadata= Request metadata as stringified JSON.
103+
-M, --metadata-file= File path for call metadata JSON file. Examples: /home/user/metadata.json or ./metadata.json.
104+
--stream-interval=0 Interval for stream requests between message sends.
105+
--stream-call-duration=0 Duration after which client will close the stream in each streaming call.
106+
--stream-call-count=0 Count of messages sent, after which client will close the stream in each streaming call.
107+
--stream-dynamic-messages In streaming calls, regenerate and apply call template data on every message send.
108+
--reflect-metadata= Reflect metadata as stringified JSON used only for reflection request.
109+
-o, --output= Output path. If none provided stdout is used.
110+
-O, --format= Output format. One of: summary, csv, json, pretty, html, influx-summary, influx-details. Default is summary.
111+
--skipFirst=0 Skip the first X requests when doing the results tally.
112+
--count-errors Count erroneous (non-OK) resoponses in stats calculations.
113+
--connections=1 Number of connections to use. Concurrency is distributed evenly among all the connections. Default is 1.
114+
--connect-timeout=10s Connection timeout for the initial connection dial. Default is 10s.
115+
--keepalive=0 Keepalive time duration. Only used if present and above 0.
116+
--name= User specified name for the test.
117+
--tags= JSON representation of user-defined string tags.
118+
--cpus=12 Number of cpu cores to use.
119+
--debug= The path to debug log file.
120+
-e, --enable-compression Enable Gzip compression on requests.
121+
-v, --version Show application version.
118122
119123
Args:
120124
[<host>] Host and port to test.

cmd/ghz/main.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,18 @@ var (
184184
si = kingpin.Flag("stream-interval", "Interval for stream requests between message sends.").
185185
Default("0").IsSetByUser(&isSISet).Duration()
186186

187+
isSCSet = false
188+
scd = kingpin.Flag("stream-call-duration", "Duration after which client will close the stream in each streaming call.").
189+
Default("0").IsSetByUser(&isSCSet).Duration()
190+
191+
isSCCSet = false
192+
scc = kingpin.Flag("stream-call-count", "Count of messages sent, after which client will close the stream in each streaming call.").
193+
Default("0").IsSetByUser(&isSCCSet).Uint()
194+
195+
isSDMSet = false
196+
sdm = kingpin.Flag("stream-dynamic-messages", "In streaming calls, regenerate and apply call template data on every message send.").
197+
Default("false").IsSetByUser(&isSDMSet).Bool()
198+
187199
isRMDSet = false
188200
rmd = kingpin.Flag("reflect-metadata", "Reflect metadata as stringified JSON used only for reflection request.").
189201
PlaceHolder(" ").IsSetByUser(&isRMDSet).String()
@@ -201,6 +213,10 @@ var (
201213
skipFirst = kingpin.Flag("skipFirst", "Skip the first X requests when doing the results tally.").
202214
Default("0").IsSetByUser(&isSkipFirstSet).Uint()
203215

216+
isCESet = false
217+
countErrors = kingpin.Flag("count-errors", "Count erroneous (non-OK) resoponses in stats calculations.").
218+
Default("false").IsSetByUser(&isCESet).Bool()
219+
204220
// Connection
205221
isConnSet = false
206222
conns = kingpin.Flag("connections", "Number of connections to use. Concurrency is distributed evenly among all the connections. Default is 1.").
@@ -426,6 +442,9 @@ func createConfigFromArgs(cfg *runner.Config) error {
426442
cfg.Metadata = metadata
427443
cfg.MetadataPath = *mdPath
428444
cfg.SI = runner.Duration(*si)
445+
cfg.StreamCallDuration = runner.Duration(*scd)
446+
cfg.StreamCallCount = *scc
447+
cfg.StreamDynamicMessages = *sdm
429448
cfg.Output = *output
430449
cfg.Format = *format
431450
cfg.ImportPaths = iPaths
@@ -451,6 +470,7 @@ func createConfigFromArgs(cfg *runner.Config) error {
451470
cfg.CEnd = *cEnd
452471
cfg.CStepDuration = runner.Duration(*cStepDuration)
453472
cfg.CMaxDuration = runner.Duration(*cMaxDuration)
473+
cfg.CountErrors = *countErrors
454474

455475
return nil
456476
}
@@ -492,10 +512,6 @@ func mergeConfig(dest *runner.Config, src *runner.Config) error {
492512
dest.SkipTLSVerify = src.SkipTLSVerify
493513
}
494514

495-
if isSkipFirstSet {
496-
dest.SkipFirst = src.SkipFirst
497-
}
498-
499515
if isInsecSet {
500516
dest.Insecure = src.Insecure
501517
}
@@ -508,6 +524,14 @@ func mergeConfig(dest *runner.Config, src *runner.Config) error {
508524
dest.CName = src.CName
509525
}
510526

527+
if isSkipFirstSet {
528+
dest.SkipFirst = src.SkipFirst
529+
}
530+
531+
if isCESet {
532+
dest.CountErrors = src.CountErrors
533+
}
534+
511535
// run
512536

513537
if isNSet {
@@ -562,6 +586,18 @@ func mergeConfig(dest *runner.Config, src *runner.Config) error {
562586
dest.SI = src.SI
563587
}
564588

589+
if isSCSet {
590+
dest.StreamCallDuration = src.StreamCallDuration
591+
}
592+
593+
if isSCCSet {
594+
dest.StreamCallCount = src.StreamCallCount
595+
}
596+
597+
if isSDMSet {
598+
dest.StreamDynamicMessages = src.StreamDynamicMessages
599+
}
600+
565601
if isOutputSet {
566602
dest.Output = src.Output
567603
}

internal/helloworld/greeter_server.go

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010

1111
context "golang.org/x/net/context"
12+
"google.golang.org/grpc/metadata"
1213
"google.golang.org/grpc/stats"
1314
)
1415

@@ -30,17 +31,20 @@ var Bidi CallType = "bidi"
3031

3132
// Greeter implements the GreeterServer for tests
3233
type Greeter struct {
33-
streamData []*HelloReply
34+
StreamData []*HelloReply
3435

3536
Stats *HWStatsHandler
3637

3738
mutex *sync.RWMutex
3839
callCounts map[CallType]int
3940
calls map[CallType][][]*HelloRequest
41+
42+
sendMutex *sync.RWMutex
43+
sendCounts map[CallType]map[int]int
4044
}
4145

42-
func randomSleep() {
43-
msCount := rand.Intn(4) + 1
46+
func randomSleep(max int) {
47+
msCount := rand.Intn(max) + 1
4448
time.Sleep(time.Millisecond * time.Duration(msCount))
4549
}
4650

@@ -62,12 +66,38 @@ func (s *Greeter) recordMessage(ct CallType, callIdx int, msg *HelloRequest) {
6266
s.calls[ct][callIdx] = append(s.calls[ct][callIdx], msg)
6367
}
6468

69+
func (s *Greeter) recordStreamSendCounter(ct CallType, callIdx int) {
70+
s.sendMutex.Lock()
71+
defer s.sendMutex.Unlock()
72+
73+
s.sendCounts[ct][callIdx] = s.sendCounts[ct][callIdx] + 1
74+
}
75+
6576
// SayHello implements helloworld.GreeterServer
6677
func (s *Greeter) SayHello(ctx context.Context, in *HelloRequest) (*HelloReply, error) {
6778
callIdx := s.recordCall(Unary)
68-
s.recordMessage(Unary, callIdx, in)
6979

70-
randomSleep()
80+
if in.GetName() == "__record_metadata__" {
81+
mdval := ""
82+
md, ok := metadata.FromIncomingContext(ctx)
83+
if ok {
84+
for k, v := range md {
85+
if k == "token" {
86+
mdval = mdval + k + ":"
87+
for _, vv := range v {
88+
mdval = mdval + vv
89+
}
90+
}
91+
}
92+
}
93+
94+
newReq := &HelloRequest{Name: in.GetName() + "||" + mdval}
95+
s.recordMessage(Unary, callIdx, newReq)
96+
} else {
97+
s.recordMessage(Unary, callIdx, in)
98+
}
99+
100+
randomSleep(4)
71101

72102
return &HelloReply{Message: "Hello " + in.Name}, nil
73103
}
@@ -77,12 +107,14 @@ func (s *Greeter) SayHellos(req *HelloRequest, stream Greeter_SayHellosServer) e
77107
callIdx := s.recordCall(ServerStream)
78108
s.recordMessage(ServerStream, callIdx, req)
79109

80-
randomSleep()
81-
82-
for _, msg := range s.streamData {
110+
for _, msg := range s.StreamData {
83111
if err := stream.Send(msg); err != nil {
84112
return err
85113
}
114+
115+
randomSleep(4)
116+
117+
s.recordStreamSendCounter(ServerStream, callIdx)
86118
}
87119

88120
return nil
@@ -92,7 +124,7 @@ func (s *Greeter) SayHellos(req *HelloRequest, stream Greeter_SayHellosServer) e
92124
func (s *Greeter) SayHelloCS(stream Greeter_SayHelloCSServer) error {
93125
callIdx := s.recordCall(ClientStream)
94126

95-
randomSleep()
127+
randomSleep(4)
96128

97129
msgCount := 0
98130

@@ -114,8 +146,6 @@ func (s *Greeter) SayHelloCS(stream Greeter_SayHelloCSServer) error {
114146
func (s *Greeter) SayHelloBidi(stream Greeter_SayHelloBidiServer) error {
115147
callIdx := s.recordCall(Bidi)
116148

117-
randomSleep()
118-
119149
for {
120150
in, err := stream.Recv()
121151
if err == io.EOF {
@@ -126,10 +156,13 @@ func (s *Greeter) SayHelloBidi(stream Greeter_SayHelloBidiServer) error {
126156
}
127157

128158
s.recordMessage(Bidi, callIdx, in)
159+
129160
msg := "Hello " + in.Name
130161
if err := stream.Send(&HelloReply{Message: msg}); err != nil {
131162
return err
132163
}
164+
165+
s.recordStreamSendCounter(ServerStream, callIdx)
133166
}
134167
}
135168

@@ -151,6 +184,14 @@ func (s *Greeter) ResetCounters() {
151184

152185
s.mutex.Unlock()
153186

187+
s.sendMutex.Lock()
188+
s.sendCounts = make(map[CallType]map[int]int)
189+
s.sendCounts[Unary] = make(map[int]int)
190+
s.sendCounts[ServerStream] = make(map[int]int)
191+
s.sendCounts[ClientStream] = make(map[int]int)
192+
s.sendCounts[Bidi] = make(map[int]int)
193+
s.sendMutex.Unlock()
194+
154195
if s.Stats != nil {
155196
s.Stats.mutex.Lock()
156197
s.Stats.connCount = 0
@@ -181,6 +222,25 @@ func (s *Greeter) GetCalls(key CallType) [][]*HelloRequest {
181222
return nil
182223
}
183224

225+
// GetSendCounts gets the stream send counts
226+
func (s *Greeter) GetSendCounts(key CallType) map[int]int {
227+
s.sendMutex.RLock()
228+
defer s.sendMutex.RUnlock()
229+
230+
val, ok := s.sendCounts[key]
231+
232+
if ok {
233+
cm := map[int]int{}
234+
for k, v := range val {
235+
cm[k] = v
236+
}
237+
238+
return cm
239+
}
240+
241+
return nil
242+
}
243+
184244
// GetConnectionCount gets the connection count
185245
func (s *Greeter) GetConnectionCount() int {
186246
return s.Stats.GetConnectionCount()
@@ -195,7 +255,7 @@ func NewGreeter() *Greeter {
195255
{Message: "Hello Sara"},
196256
}
197257

198-
greeter := &Greeter{streamData: streamData, mutex: &sync.RWMutex{}}
258+
greeter := &Greeter{StreamData: streamData, mutex: &sync.RWMutex{}, sendMutex: &sync.RWMutex{}}
199259
greeter.ResetCounters()
200260

201261
return greeter

0 commit comments

Comments
 (0)