Skip to content

Add feature usage tracking to the User-Agent header - #882

Merged
ubaskota merged 3 commits into
smithy-lang:developfrom
ubaskota:user_agent_metrics_scaffold
Nov 3, 2026
Merged

ubaskota merged 3 commits into
smithy-lang:developfrom
ubaskota:user_agent_metrics_scaffold

Conversation

@ubaskota

@ubaskota ubaskota commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Issue #, if available:

Description of changes:
Add a way to record which SDK features a call uses and report them in the User-Agent header's m/ section. Features are tracked per call with register_feature_id and the AwsFeatureId enum, and the AWS user agent interceptor appends them to the header after signing, without adding them twice on retries. This PR tracks features for retry mode and the
endpoint override.

Testing:
Added unit tests for registration, encoding and the header update, including retries, and integration tests with a generated client. Verified that all existing and new tests pass.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@ubaskota
ubaskota requested a review from a team as a code owner November 1, 2026 21:52
Comment on lines +8 to +12
from smithy_core.aio.retries import (
RETRY_STRATEGY,
AsyncAdaptiveRetryStrategy,
AsyncStandardRetryStrategy,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we should bump smithy-aws-core's smithy-core dependency to the release that introduces RETRY_STRATEGY. It still allows 0.9.x, so a valid installation can fail while importing this module

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We generally should not manually bump our dependency pins since this is handled automatically by our release infra. For something like this that is required by a dependent, a minor version should be triggered by either a breaking or feature changelog entry. Long term we can make this more fine-grain (this is probably more like an enhancement change, but that triggers a patch bump right now. We can probably add a new field in the future to override the bump type since not every feature is a minor bump and not every enhancement is a patch bump).

If this is merged before out next release, nothing should be needed since there already pending breaking changes and features for smithy-core. But adding a feature entry might be safer incase this is released after

"smithy_aws_core.aio.endpoints.standard_regional": {
"AsyncEndpointResolver": "rex-protocol[async]",
"AsyncStandardRegionalEndpointsResolver": "class[async]",
"AwsFeatureId": "rex-class",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we keep these implementation imports private? The snapshot is picking up a bunch of new exports from user_agent, standard_regional, and aio.client that don’t seem intentional

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rex (re-exports) moving around should be ok. They are tracked in the API snapshot but I think customers understand not to import things that aren't part of a module's __ALL__ official exports.

@jonathan343 jonathan343 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @ubaskota! Overall this PR looks good. I have a few questions on things I'm confused about.

encoded = ""
for feature_id in feature_ids:
candidate = f"{encoded},{feature_id.value}" if encoded else feature_id.value
if len(candidate.encode("utf-8")) > MAX_ENCODED_FEATURE_IDS_BYTES:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Question: Why is .encode("utf-8") needed here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This measures the length of the encoded size. We do sth similar in botocore as well. Metric values are ASCII, so in practice it's the same as len(candidate). Happy to simplify if you prefer.

# fallback, for example with a signer that doesn't copy them, if a retry
# still has the header written here, start from its value before m/ was
# added, so m/ is never added twice and nothing else is removed.
previous = context.properties.get(_WRITTEN_USER_AGENT)

Copy link
Copy Markdown
Contributor

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 by the usage of _WRITTEN_USER_AGENT. Can you explain what case it's protecting against?

@ubaskota ubaskota Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I initially thought the business metrics could get duplicated when a request is retried, so added this check to prevent that. But after further investigation, it looks like the duplication doesn't happen. The SigV4 signer works on a copy of the request's headers, and the generic interceptor rewrites User-Agent on every attempt, so each retry starts without m/. I've removed the check and updated the tests.

@ubaskota
ubaskota merged commit 3b7514a into smithy-lang:develop Nov 3, 2026
48 checks passed
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.

4 participants