diff --git a/README.md b/README.md index 4b70e06c..302a9f53 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # ghz -[![Release](https://img.shields.io/github/release/bojand/ghz.svg?style=flat-square)](https://github.com/bojand/ghz/releases/latest) +[![Release](https://img.shields.io/github/release/bojand/ghz.svg?style=flat-square)](https://github.com/bojand/ghz/releases/latest) [![Build Status](https://img.shields.io/circleci/project/github/bojand/ghz/master.svg?style=flat-square)](https://circleci.com/gh/bojand/ghz) [![Go Report Card](https://goreportcard.com/badge/github.com/bojand/ghz?style=flat-square)](https://goreportcard.com/report/github.com/bojand/ghz) [![License](https://img.shields.io/github/license/bojand/ghz.svg?style=flat-square)](https://raw.githubusercontent.com/bojand/ghz/master/LICENSE) @@ -29,7 +29,7 @@ Options: -proto The Protocol Buffer .proto file. -protoset The compiled protoset file. Alternative to proto. -proto takes precedence. --call A fully-qualified method name in 'package/service/method' or 'package.service.method' format. +-call A fully-qualified method name in 'package.Service/method' or 'package.Service.Method' format. -i 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. @@ -60,7 +60,7 @@ Options: -m Request metadata as stringified JSON. -M Path for call metadata JSON file. Examples: /home/user/metadata.json or ./metadata.json. --si Stream interval duration. Spread stream sends by given amount. +-si Stream interval duration. Spread stream sends by given amount. Only applies to client and bidi streaming calls. Example: 100ms -o Output path. If none provided stdout is used. diff --git a/cmd/ghz/main.go b/cmd/ghz/main.go index ee96e248..bcd4c05a 100644 --- a/cmd/ghz/main.go +++ b/cmd/ghz/main.go @@ -24,7 +24,7 @@ var ( proto = flag.String("proto", "", `The Protocol Buffer .proto file.`) protoset = flag.String("protoset", "", `The compiled protoset file. Alternative to proto. -proto takes precedence.`) - call = flag.String("call", "", `A fully-qualified method name in 'package/service/method' or 'package.service.method' format.`) + call = flag.String("call", "", `A fully-qualified method name in 'package.Service/method' or 'package.Service.Method' format.`) paths = flag.String("i", "", "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.") cacert = flag.String("cacert", "", "File containing trusted root certificates for verifying the server.") @@ -71,10 +71,10 @@ Options: -proto The Protocol Buffer .proto file. -protoset The compiled protoset file. Alternative to proto. -proto takes precedence. --call A fully-qualified method name in 'package/service/method' or 'package.service.method' format. +-call A fully-qualified method name in 'package.Service/Method' or 'package.Service.Method' format. -i 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. - + -cacert File containing trusted root certificates for verifying the server. -cert File containing client certificate (public key), to present to the server. Must also provide -key option. -key File containing client private key, to present to the server. Must also provide -cert option. @@ -83,7 +83,7 @@ Options: -insecure Use plaintext and insecure connection. -authority Value to be used as the :authority pseudo-header. Only works if -insecure is used. --c Number of requests to run concurrently. +-c Number of requests to run concurrently. Total number of requests cannot be smaller than the concurrency level. Default is 50. -n Number of requests to run. Default is 200. -q Rate limit, in queries per second (QPS). Default is no rate limit. @@ -102,7 +102,7 @@ Options: -m Request metadata as stringified JSON. -M Path for call metadata JSON file. Examples: /home/user/metadata.json or ./metadata.json. --si Stream interval duration. Spread stream sends by given amount. +-si Stream interval duration. Spread stream sends by given amount. Only applies to client and bidi streaming calls. Example: 100ms -o Output path. If none provided stdout is used. diff --git a/protodesc/protodesc.go b/protodesc/protodesc.go index f237d606..2d962d96 100644 --- a/protodesc/protodesc.go +++ b/protodesc/protodesc.go @@ -1,6 +1,7 @@ package protodesc import ( + "errors" "fmt" "io/ioutil" "path/filepath" @@ -12,6 +13,8 @@ import ( "github.com/jhump/protoreflect/desc/protoparse" ) +var errNoMethodNameSpecified = errors.New("no method name specified") + // GetMethodDescFromProto gets method descritor for the given call symbol from proto file given my path proto // imports is used for import paths in parsing the proto file func GetMethodDescFromProto(call, proto string, imports []string) (*desc.MethodDescriptor, error) { @@ -64,9 +67,9 @@ func GetMethodDescFromProtoSet(call, protoset string) (*desc.MethodDescriptor, e } func getMethodDesc(call string, files map[string]*desc.FileDescriptor) (*desc.MethodDescriptor, error) { - svc, mth := parseSymbol(call) - if svc == "" || mth == "" { - return nil, fmt.Errorf("given method name %q is not in expected format: 'service/method' or 'service.method'", call) + svc, mth, err := parseServiceMethod(call) + if err != nil { + return nil, err } dsc, err := findServiceSymbol(files, svc) @@ -123,13 +126,39 @@ func findServiceSymbol(resolved map[string]*desc.FileDescriptor, fullyQualifiedN return nil, fmt.Errorf("cannot find service %q", fullyQualifiedName) } -func parseSymbol(svcAndMethod string) (string, string) { - pos := strings.LastIndex(svcAndMethod, "/") - if pos < 0 { - pos = strings.LastIndex(svcAndMethod, ".") +// parseServiceMethod parses the fully-qualified service name without a leading "." +// and the method name from the input string. +// +// valid inputs: +// package.Service.Method +// .package.Service.Method +// package.Service/Method +// .package.Service/Method +func parseServiceMethod(svcAndMethod string) (string, string, error) { + if len(svcAndMethod) == 0 { + return "", "", errNoMethodNameSpecified + } + if svcAndMethod[0] == '.' { + svcAndMethod = svcAndMethod[1:] + } + if len(svcAndMethod) == 0 { + return "", "", errNoMethodNameSpecified + } + switch strings.Count(svcAndMethod, "/") { + case 0: + pos := strings.LastIndex(svcAndMethod, ".") if pos < 0 { - return "", "" + return "", "", newInvalidMethodNameError(svcAndMethod) } + return svcAndMethod[:pos], svcAndMethod[pos+1:], nil + case 1: + split := strings.Split(svcAndMethod, "/") + return split[0], split[1], nil + default: + return "", "", newInvalidMethodNameError(svcAndMethod) } - return svcAndMethod[:pos], svcAndMethod[pos+1:] +} + +func newInvalidMethodNameError(svcAndMethod string) error { + return fmt.Errorf("method name must be package.Service.Method or package.Service/Method: %q", svcAndMethod) } diff --git a/protodesc/protodesc_test.go b/protodesc/protodesc_test.go index fe8d5311..7c9c678a 100644 --- a/protodesc/protodesc_test.go +++ b/protodesc/protodesc_test.go @@ -87,3 +87,29 @@ func TestProtodesc_GetMethodDescFromProtoSet(t *testing.T) { assert.NotNil(t, md) }) } + +func TestParseServiceMethod(t *testing.T) { + testParseServiceMethodSuccess(t, "package.Service.Method", "package.Service", "Method") + testParseServiceMethodSuccess(t, ".package.Service.Method", "package.Service", "Method") + testParseServiceMethodSuccess(t, "package.Service/Method", "package.Service", "Method") + testParseServiceMethodSuccess(t, ".package.Service/Method", "package.Service", "Method") + testParseServiceMethodSuccess(t, "Service.Method", "Service", "Method") + testParseServiceMethodSuccess(t, ".Service.Method", "Service", "Method") + testParseServiceMethodSuccess(t, "Service/Method", "Service", "Method") + testParseServiceMethodSuccess(t, ".Service/Method", "Service", "Method") + testParseServiceMethodError(t, "") + testParseServiceMethodError(t, ".") + testParseServiceMethodError(t, "package/Service/Method") +} + +func testParseServiceMethodSuccess(t *testing.T, svcAndMethod string, expectedService string, expectedMethod string) { + service, method, err := parseServiceMethod(svcAndMethod) + assert.NoError(t, err) + assert.Equal(t, expectedService, service) + assert.Equal(t, expectedMethod, method) +} + +func testParseServiceMethodError(t *testing.T, svcAndMethod string) { + _, _, err := parseServiceMethod(svcAndMethod) + assert.Error(t, err) +} diff --git a/www/docs/options.md b/www/docs/options.md index 67a2a07c..98b2f65c 100644 --- a/www/docs/options.md +++ b/www/docs/options.md @@ -3,7 +3,7 @@ id: options title: Options Reference --- -The `ghz` command line has numerous command line options. You can run `ghz --help` to view all available options. +The `ghz` command line has numerous command line options. You can run `ghz --help` to view all available options. @@ -22,7 +22,7 @@ protoc --proto_path=. --descriptor_set_out=bundle.protoset *.proto ### `-call` -A fully-qualified method name in 'package/service/method' or 'package.service.method' format. For example: `helloworld.Greeter.SayHello`. +A fully-qualified method name in 'package.Service/Method' or 'package.Service.Method' format. For example: `helloworld.Greeter.SayHello`. ### `-cacert` @@ -58,15 +58,15 @@ Path to the JSON or TOML [config file](example_config.md) that specifies all the ### `-c` -Number of requests to run concurrently. Total number of requests cannot be smaller than the concurrency level. Default is `50`. For example to do requests in series without any concurrency set to `1`. +Number of requests to run concurrently. Total number of requests cannot be smaller than the concurrency level. Default is `50`. For example to do requests in series without any concurrency set to `1`. ### `-n` -The total number of requests to run. Default is `200`. The combination of `-c` and `-n` are critical in how the benchmarking is done. `ghz` takes the `-c` argument and spawns that make worker goroutines. In parallel these goroutines each do their share (`c / n`) requests. So for example with the default `-c 50 -n 200` options we would spawn `50` goroutines which in parallel each do `40` requests. +The total number of requests to run. Default is `200`. The combination of `-c` and `-n` are critical in how the benchmarking is done. `ghz` takes the `-c` argument and spawns that make worker goroutines. In parallel these goroutines each do their share (`c / n`) requests. So for example with the default `-c 50 -n 200` options we would spawn `50` goroutines which in parallel each do `40` requests. ### `-q` -Rate limit for each worker goroutine in queries per second (QPS). Default is no rate limit. This essentially spaces out the requests by adding a timeout to each worker before it attempts each call. +Rate limit for each worker goroutine in queries per second (QPS). Default is no rate limit. This essentially spaces out the requests by adding a timeout to each worker before it attempts each call. ### `-t` @@ -82,7 +82,7 @@ Maximum duration of application to send requests with `n` setting respected. If ### `-d` -The call data as stringified JSON. If the value is `@` then the request contents are read from standard input (stdin). Example: `-d '{"name":"Bob"}'`. +The call data as stringified JSON. If the value is `@` then the request contents are read from standard input (stdin). Example: `-d '{"name":"Bob"}'`. For client streaming or bi-directional calls we can accept a JSON array of messages, and each element representing a single message within the stream call. For example: `-d '[{"name":"Joe"},{"name":"Kate"},{"name":"Sara"}]'` can be used as input for a client streaming or bidi call. In case of streaming calls if a single object is given for data then it is automatically converted to an array with single element. For example `-d '{"name":"Joe"}'` is equivalent to `-d '[{"name":"Joe"}]`. diff --git a/www/docs/usage.md b/www/docs/usage.md index 969a6d77..90e25014 100644 --- a/www/docs/usage.md +++ b/www/docs/usage.md @@ -11,7 +11,7 @@ Options: -proto The Protocol Buffer .proto file. -protoset The compiled protoset file. Alternative to proto. -proto takes precedence. --call A fully-qualified method name in 'package/service/method' or 'package.service.method' format. +-call A fully-qualified method name in 'package.Service/Method' or 'package.Service.Method' format. -i 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. @@ -42,7 +42,7 @@ Options: -m Request metadata as stringified JSON. -M Path for call metadata JSON file. Examples: /home/user/metadata.json or ./metadata.json. --si Stream interval duration. Spread stream sends by given amount. +-si Stream interval duration. Spread stream sends by given amount. Only applies to client and bidi streaming calls. Example: 100ms -o Output path. If none provided stdout is used.