Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib/netext/grpcext: Split the gRPC module #2484

Merged
merged 4 commits into from
Apr 21, 2022
Merged

lib/netext/grpcext: Split the gRPC module #2484

merged 4 commits into from
Apr 21, 2022

Conversation

codebien
Copy link
Contributor

@codebien codebien commented Apr 7, 2022

The split of the gRPC business logic from the js/k6/grpc module to a netext package.
It enables a simpler process for extending the networking+metric features for gRPC without the k6/js dependencies.

An example of how the API of this package can be used for creating gRPC extensions: codebien/xk6-grpc-example

Closes #2315

@codebien codebien self-assigned this Apr 7, 2022
@codebien codebien force-pushed the grpcext branch 2 times, most recently from 83c999d to 12530dd Compare April 8, 2022 09:47
@codebien codebien marked this pull request as ready for review April 8, 2022 10:11
@codebien codebien requested a review from na-- April 8, 2022 13:02
Comment on lines 89 to 90
// TODO: should we support grpc.CallOption?
func (c *Conn) Invoke(ctx context.Context, method string, md metadata.MD, req Request) (*Response, error) {
Copy link
Member

@na-- na-- Apr 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we support grpc.CallOption?

Yes, I think this might be a good idea, given that they are present in grpc.ClientConn.Invoke(). Can you think of a reason why we shouldn't support them now, even if we don't use them yet?

Also, do we need a method parameter here when we have a MethodDescriptor in the Request?

Copy link
Contributor Author

@codebien codebien Apr 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, do we need a method parameter here when we have a MethodDescriptor in the Request?

Unfortunately, yes. The FullName of the method descriptor returns the full name domain as a dot notation. Instead, Invoke wants the package.serviceName/methodName format. I don't know if the simple replacement of the last dot with a slash could be fine.
Apparently, also the protoc generator generates the url instead of get+replace of the method descriptor's full name.

The Descriptor's Parent() method could be optimal for the descriptor-based generation but the doc says that it's optional and it could return nil. 🤷‍♂️

I'm thinking if we could change the name from method to url like our js module's documentation does.

@@ -353,49 +253,22 @@ func (c *Client) dial(
reflect bool,
options ...grpc.DialOption,
) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Client.dial() code doesn't seem it has anything to do with JavaScript, what is the reason to not move it to grpcext?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It opens the connection then it routes through reflection if requested from the parameter, I think this way of connecting it's a property of the js module. 🤔 It could be a good idea to change the name of the method that probably it's not accurate after the refactor because the real dialling part is now delegated to grpcext. This method instead is mostly the core part of the Connect method.

Copy link
Contributor

@olegbespalov olegbespalov Apr 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it seems like the vast majority of the logic related to the reflection is still can be moved to a grpcext.

I belive we can even make a private a ReflectionClient. What js nodule needs are the *descriptorpb.FileDescriptorSet, so the grpcext.Conn can has a method that tries to load them. What do your think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And by the number of the logic, the current Client.dial is more like Client.reflect than dial 😅

Base automatically changed from grpc-connect-refactor to master April 13, 2022 14:12
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you consider using github.com/gogo/protobuf/proto which has faster mashalling/unmarshalling?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @cygnrh, thanks for your review.

Unfortunately, we would prefer to not include it, we are very meticulous in adding new dependencies to k6, you can read some motivations in Dependencies.md.
We would prefer to fix allowing the marshaler and unmarshaler to be injected, it would allow us to remove also the tightly coupled JSON dependency. I opened an issue for it #2497

If you have relevant observations for the grpc module, it would be helpful to get your feedback in a new issue or in #1846.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't observed performance issue since we only did the initial test. If we do see anything as k6 gRPC being more widely adopted, I'll post in the thread #1846.

}
return &Conn{
raw: conn,
}, nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about including options in this struct so we don't have to pass options again for Invoke?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think WithDefaultCallOptions covers this case. Could it work for you?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused that options ...grpc.DialOption is passed in from Dial and Invoke. If options is part of the Conn struct, Invoke can use it as a receiver function.

Copy link
Contributor Author

@codebien codebien Apr 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you add a slice DialOption field?

type Conn struct {
        Options []grpc.DialOption

	raw clientConnCloser
}

and, calling the Conn.Invoke method, would you like to not pass the Call Options?

options := []grpc.DialOption{
   .....
}
conn, err := Dial(ctx, addr, options...)
err = conn.Invoke(ctx, url, md)

If yes, then the issue is mainly related to the different types involved. The eventual Conn.Options couldn't be used on the underhood call grpc.Invoke because the Invoke method wants the CallOption type.

Please, correct me if I misunderstood.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. They are different types of options. That makes sense.

It splits the gRPC business logic from the js/k6/grpc module to a netext
package.
It will be easier to extend the networking+metric features for gRPC in
isolated environment and/or create extension not strictly depending on
the js feature.
@codebien codebien requested a review from na-- April 19, 2022 08:14
@na-- na-- removed their request for review April 20, 2022 12:06
state := c.vu.State()
if state == nil {
return nil, errInvokeRPCInInitContext
return nil, common.NewInitContextError("invoking RPC methods in the init context is not supported")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return nil, common.NewInitContextError("invoking RPC methods in the init context is not supported")
return nil, common.NewInitContextError("invoking gRPC methods in the init context is not supported")

Maybe use this? Just for consistency?

@@ -353,49 +253,22 @@ func (c *Client) dial(
reflect bool,
options ...grpc.DialOption,
) error {
Copy link
Contributor

@olegbespalov olegbespalov Apr 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it seems like the vast majority of the logic related to the reflection is still can be moved to a grpcext.

I belive we can even make a private a ReflectionClient. What js nodule needs are the *descriptorpb.FileDescriptorSet, so the grpcext.Conn can has a method that tries to load them. What do your think?

Copy link
Contributor

@mstoykov mstoykov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM and IMO we can continue iterating in the next release on any of the currently commented stuff

@codebien codebien merged commit 187d336 into master Apr 21, 2022
@codebien codebien deleted the grpcext branch April 21, 2022 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide an interface to allow customized gRPC options for the gRPC client
5 participants