From e544b9e66f8b470dfc31b2d0452b603fe27167d3 Mon Sep 17 00:00:00 2001 From: Sergei Vorobev Date: Fri, 14 Aug 2020 08:02:42 -0700 Subject: [PATCH] Print size of the message in very verbose mode (#181) Adds -vv flag for "very verbose". Includes estimated message size when enabled. The size is an estimate because it is the canonical size for the proto message, but not necessarily its actual on-the-wire size. --- cmd/grpcurl/grpcurl.go | 20 ++++++++++++--- format.go | 57 ++++++++++++++++++++++++++---------------- format_test.go | 20 ++++++++++++--- go.sum | 2 ++ 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/cmd/grpcurl/grpcurl.go b/cmd/grpcurl/grpcurl.go index fd00511..66b2990 100644 --- a/cmd/grpcurl/grpcurl.go +++ b/cmd/grpcurl/grpcurl.go @@ -135,6 +135,8 @@ var ( When describing messages, show a template of input data.`)) verbose = flags.Bool("v", false, prettify(` Enable verbose output.`)) + veryVerbose = flags.Bool("vv", false, prettify(` + Enable very verbose output.`)) serverName = flags.String("servername", "", prettify(` Override server name when validating TLS certificate. This flag is ignored if -plaintext or -insecure is used. @@ -316,6 +318,14 @@ func main() { invoke = true } + verbosityLevel := 0 + if *verbose { + verbosityLevel = 1 + } + if *veryVerbose { + verbosityLevel = 2 + } + var symbol string if invoke { if len(args) == 0 { @@ -651,7 +661,7 @@ func main() { // if not verbose output, then also include record delimiters // between each message, so output could potentially be piped // to another grpcurl process - includeSeparators := !*verbose + includeSeparators := verbosityLevel == 0 options := grpcurl.FormatOptions{ EmitJSONDefaultFields: *emitDefaults, IncludeTextSeparator: includeSeparators, @@ -661,7 +671,11 @@ func main() { if err != nil { fail(err, "Failed to construct request parser and formatter for %q", *format) } - h := grpcurl.NewDefaultEventHandler(os.Stdout, descSource, formatter, *verbose) + h := &grpcurl.DefaultEventHandler{ + Out: os.Stdout, + Formatter: formatter, + VerbosityLevel: verbosityLevel, + } err = grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), h, rf.Next) if err != nil { @@ -676,7 +690,7 @@ func main() { if h.NumResponses != 1 { respSuffix = "s" } - if *verbose { + if verbosityLevel > 0 { fmt.Printf("Sent %d request%s and received %d response%s\n", reqCount, reqSuffix, h.NumResponses, respSuffix) } if h.Status.Code() != codes.OK { diff --git a/format.go b/format.go index 54cd5f8..98d6a24 100644 --- a/format.go +++ b/format.go @@ -409,10 +409,12 @@ func RequestParserAndFormatterFor(format Format, descSource DescriptorSource, em // safe for use with InvokeRPC as long as NumResponses and Status are not read // until the call to InvokeRPC completes. type DefaultEventHandler struct { - out io.Writer - descSource DescriptorSource - formatter func(proto.Message) (string, error) - verbose bool + Out io.Writer + Formatter Formatter + // 0 = default + // 1 = verbose + // 2 = very verbose + VerbosityLevel int // NumResponses is the number of responses that have been received. NumResponses int @@ -424,54 +426,65 @@ type DefaultEventHandler struct { // NewDefaultEventHandler returns an InvocationEventHandler that logs events to // the given output. If verbose is true, all events are logged. Otherwise, only // response messages are logged. +// +// Deprecated: NewDefaultEventHandler exists for compatability. +// It doesn't allow fine control over the `VerbosityLevel` +// and provides only 0 and 1 options (which corresponds to the `verbose` argument). +// Use DefaultEventHandler{} initializer directly. func NewDefaultEventHandler(out io.Writer, descSource DescriptorSource, formatter Formatter, verbose bool) *DefaultEventHandler { + verbosityLevel := 0 + if verbose { + verbosityLevel = 1 + } return &DefaultEventHandler{ - out: out, - descSource: descSource, - formatter: formatter, - verbose: verbose, + Out: out, + Formatter: formatter, + VerbosityLevel: verbosityLevel, } } var _ InvocationEventHandler = (*DefaultEventHandler)(nil) func (h *DefaultEventHandler) OnResolveMethod(md *desc.MethodDescriptor) { - if h.verbose { - txt, err := GetDescriptorText(md, h.descSource) + if h.VerbosityLevel > 0 { + txt, err := GetDescriptorText(md, nil) if err == nil { - fmt.Fprintf(h.out, "\nResolved method descriptor:\n%s\n", txt) + fmt.Fprintf(h.Out, "\nResolved method descriptor:\n%s\n", txt) } } } func (h *DefaultEventHandler) OnSendHeaders(md metadata.MD) { - if h.verbose { - fmt.Fprintf(h.out, "\nRequest metadata to send:\n%s\n", MetadataToString(md)) + if h.VerbosityLevel > 0 { + fmt.Fprintf(h.Out, "\nRequest metadata to send:\n%s\n", MetadataToString(md)) } } func (h *DefaultEventHandler) OnReceiveHeaders(md metadata.MD) { - if h.verbose { - fmt.Fprintf(h.out, "\nResponse headers received:\n%s\n", MetadataToString(md)) + if h.VerbosityLevel > 0 { + fmt.Fprintf(h.Out, "\nResponse headers received:\n%s\n", MetadataToString(md)) } } func (h *DefaultEventHandler) OnReceiveResponse(resp proto.Message) { h.NumResponses++ - if h.verbose { - fmt.Fprint(h.out, "\nResponse contents:\n") + if h.VerbosityLevel > 1 { + fmt.Fprintf(h.Out, "\nEstimated response size: %d bytes\n", proto.Size(resp)) } - if respStr, err := h.formatter(resp); err != nil { - fmt.Fprintf(h.out, "Failed to format response message %d: %v\n", h.NumResponses, err) + if h.VerbosityLevel > 0 { + fmt.Fprint(h.Out, "\nResponse contents:\n") + } + if respStr, err := h.Formatter(resp); err != nil { + fmt.Fprintf(h.Out, "Failed to format response message %d: %v\n", h.NumResponses, err) } else { - fmt.Fprintln(h.out, respStr) + fmt.Fprintln(h.Out, respStr) } } func (h *DefaultEventHandler) OnReceiveTrailers(stat *status.Status, md metadata.MD) { h.Status = stat - if h.verbose { - fmt.Fprintf(h.out, "\nResponse trailers received:\n%s\n", MetadataToString(md)) + if h.VerbosityLevel > 0 { + fmt.Fprintf(h.Out, "\nResponse trailers received:\n%s\n", MetadataToString(md)) } } diff --git a/format_test.go b/format_test.go index 3e517e3..1a36059 100644 --- a/format_test.go +++ b/format_test.go @@ -120,12 +120,14 @@ func TestHandler(t *testing.T) { for _, format := range []Format{FormatJSON, FormatText} { for _, numMessages := range []int{1, 3} { - for _, verbose := range []bool{true, false} { + for verbosityLevel := 0; verbosityLevel <= 2; verbosityLevel++ { name := fmt.Sprintf("%s, %d message(s)", format, numMessages) - if verbose { - name += ", verbose" + if verbosityLevel > 0 { + name += fmt.Sprintf(", verbosityLevel=%d", verbosityLevel) } + verbose := verbosityLevel > 0 + _, formatter, err := RequestParserAndFormatter(format, source, nil, FormatOptions{IncludeTextSeparator: !verbose}) if err != nil { t.Errorf("Failed to create parser and formatter: %v", err) @@ -133,7 +135,11 @@ func TestHandler(t *testing.T) { } var buf bytes.Buffer - h := NewDefaultEventHandler(&buf, source, formatter, verbose) + h := &DefaultEventHandler{ + Out: &buf, + Formatter: formatter, + VerbosityLevel: verbosityLevel, + } h.OnResolveMethod(md) h.OnSendHeaders(reqHeaders) @@ -148,6 +154,9 @@ func TestHandler(t *testing.T) { expectedOutput += verbosePrefix } for i := 0; i < numMessages; i++ { + if verbosityLevel > 1 { + expectedOutput += verboseResponseSize + } if verbose { expectedOutput += verboseResponseHeader } @@ -226,6 +235,9 @@ Response trailers received: a: 1 b: 2 c: 3 +` + verboseResponseSize = ` +Estimated response size: 100 bytes ` verboseResponseHeader = ` Response contents: diff --git a/go.sum b/go.sum index 6e6a698..8173527 100644 --- a/go.sum +++ b/go.sum @@ -365,6 +365,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -444,6 +445,7 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=