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

stats: add client side user agent to outgoing header #3331

Merged
merged 14 commits into from
Jan 31, 2020

Conversation

piotrkowalczuk
Copy link
Contributor

@piotrkowalczuk piotrkowalczuk commented Jan 23, 2020

This PR is a PoC and a proposal at the same time. It adds UserAgent field to stats.RPCTagInfo that carry the same information that is passed through metadata user-agent field. I have tried a similar approach with stats.ConnTagInfo however metadata is not yet available when TagConn is called on the server-side.

My main motivation to submit this change was to make a transition of my instrumentation library from Interceptors to stats.Handler possible. Since user-agent stores information about gRPC version and at the same time it may carry similar information about the client app name and its version, I think it might be very relevant information, e.g. to measure how newly deployed version behaves (canary deployment).

Related issue piotrkowalczuk/promgrpc#11

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Jan 23, 2020

CLA Check
The committers are authorized under a signed CLA.

@menghanl
Copy link
Contributor

On the server side, you should be able to get the metadata from context (metadata.FromIncomingContext(ctx)) and read the content.

On the client side, outgoing metadata doesn't have user-agent (it's appended later by the transport), so either this change, or we need to add it to the metadata.

In your interceptor based solution, how did you get user-agent on the client side?

@dfawley
Copy link
Member

dfawley commented Jan 23, 2020

To get the gRPC version on this client side, you could use grpc.Version. Do you explicitly need the user agent string, even when it's overridden?

@piotrkowalczuk
Copy link
Contributor Author

piotrkowalczuk commented Jan 23, 2020

On the client side, outgoing metadata doesn't have user-agent (it's appended later by the transport), so either this change, or we need to add it to the metadata.

I like the idea of passing the data using an already existing mechanism.

In your interceptor based solution, how did you get user-agent on the client side?

I haven't, as you described metadata is not available.

To get the gRPC version on this client side, you could use grpc.Version. Do you explicitly need the user agent string, even when it's overridden?

Especially when it is overridden. From an application maintainer point of view, I expect to change my code more frequently than gRPC version, so user-agent section that describes my own app is more interesting to me. Having gRPC version included is useful as well though.

@menghanl
Copy link
Contributor

With a second thought:
user-agent is kind of at a grey area. It's not a gRPC concept, but an http/http2 concept. So if the transport is changed to another protocol, user-agent wouldn't be appropriate.
This is the main reason user-agent is not set in client metadata, but is set by the client transport.

For the same reason, I would prefer to keep the transport specific details independent from the APIs gRPC exposes. (Though we are already violating that by having a dial option to set user agent, but that's more for historical reasons, and a better way that I would do today is to have a generic dial option to set transport configs that only a specific transport can understand)

Adding user-agent to outgoing metadata at the gRPC layer would break the layers.


Will you need user-agent on the client side?
You mentioned that you didn't have it in the interceptor based solution. I also think it's less useful comparing to the server side.

@piotrkowalczuk
Copy link
Contributor Author

piotrkowalczuk commented Jan 23, 2020

Will you need user-agent on the client side?

The main reason to have such value on both sides is to be able to match metrics with each other or being able to distinguish metrics (e.g. number of outgoing requests) produced by different version of an app. Something like this might be done on the server-side, however:

  • it is not guaranteed that app A will always talk to the app B
  • if contract B implemented in team B by app B would one day be implemented by app B2 it might happen that dashboard/alerts of app A in team A would break. In other words I would prefer to keep metrics of team B private.

I completely get your point, and it is clear to me that this is not the best way to address my problem. Do you consider to introduce any other mechanism that would serve a similar purpose?

@menghanl
Copy link
Contributor

Another option is to add user-agent to OutHeader. Today, it's only a copy of the metadata, so it doesn't have user-agent.
We can manually append it. (There are other fields sent by the client, but not in OutHeader. I think it should be OK to leave these alone for now, until someone asks for them. And some of them are oauth headers that probably shouldn't be exposed).

This means you won't get the info in TagRPC, but will later in HandleRPC.

@dfawley
Copy link
Member

dfawley commented Jan 24, 2020

For the same reason, I would prefer to keep the transport specific details independent from the APIs gRPC exposes

I would say that some of the stats API already contains HTTP-transport-specific details (e.g. wire length). So adding something transport-specific to that API is not out of the question IMO. However, we should be careful about how we expose it.

Adding user-agent to outgoing metadata at the gRPC layer would break the layers.

I agree with this. If we add it, it should be done inside the transport instead (and include the full header value, matching what would be seen by the server).

@piotrkowalczuk
Copy link
Contributor Author

This means you won't get the info in TagRPC, but will later in HandleRPC.

A problem I can see with this approach is that such metadata would not be available in all HandleRPC calls.*stats.OutHeader fires after *stats.Begin.

@menghanl
Copy link
Contributor

The timing problem is a little tricky.
On the client side, TagRPC, HandleRPC Begin, and HandleRPC OutHeader should all happen about the same time.

Would it be OK to leave user-agent empty, and populate it later while handling OutHeader? Or even log the begin while handling OutHeader? This should be early enough for both unary and streaming RPCs.

@piotrkowalczuk
Copy link
Contributor Author

piotrkowalczuk commented Jan 28, 2020

@menghanl makes sense. Done.

Copy link
Contributor

@menghanl menghanl left a comment

Choose a reason for hiding this comment

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

Thanks for the fix.

Seems vet isn't happy for some reason (travis log doesn't show the actually error...)
Looks like it failed gofmt -s -d -l .. Try it locally and see if there's any diff? You can also just run vet.sh.

stats/stats_test.go Outdated Show resolved Hide resolved
@menghanl menghanl added the Type: Feature New features or improvements in behavior label Jan 29, 2020
@menghanl menghanl added this to the 1.28 Release milestone Jan 29, 2020
@@ -681,13 +681,15 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
}
if t.statsHandler != nil {
header, _, _ := metadata.FromOutgoingContextRaw(ctx)
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, I believe this is a bug to call Raw here.
This should be just FromOutgoingContext(), otherwise it will miss the metadata that's appended.
Can you fix it, too? Thanks!

And I believe if you change this, the copy() won't be necessary anymore, because FromOutgoingContext always does a join. (The comment of the function also needs update, but that's another issue).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

@menghanl menghanl left a comment

Choose a reason for hiding this comment

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

Thanks for all the changes! LGTM.

@menghanl menghanl changed the title stats.Handler access to client user agent through stats.RPCTagInfo stats: add client side user agent to outgoing header Jan 31, 2020
@menghanl menghanl merged commit 7621679 into grpc:master Jan 31, 2020
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 25, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Type: Feature New features or improvements in behavior
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants