Skip to content

Add request-level MetricPublisher support to the S3 CRT client - #7316

Merged
jencymaryjoseph merged 4 commits into
feature/master/S3CRTMetricPublisherfrom
jencyjos/s3crt/request-metrics
Aug 27, 2026
Merged

Add request-level MetricPublisher support to the S3 CRT client#7316
jencymaryjoseph merged 4 commits into
feature/master/S3CRTMetricPublisherfrom
jencyjos/s3crt/request-metrics

Conversation

@jencymaryjoseph

@jencymaryjoseph jencymaryjoseph commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Motivation and Context

Follow-up to the client-level MetricPublisher support (#7299). This adds request-level metric publishers to the S3 CRT-based client: a publisher attached to a single request via overrideConfiguration(o -> o.addMetricPublisher(...)) now receives that request's CRT telemetry, matching the standard S3 client. When a request sets its own publishers, they take precedence over the client-level ones for that request; an empty request-level list falls back to the client-level publishers, same as the standard client.

Modifications

Request-level publishers are moved off the request's override configuration and routed to the CRT
transport, so the inner standard client stays publisher-free (no hollow, duplicate ApiCall metric) and
telemetry is published from onTelemetry, once per underlying CRT request.

  • DefaultS3CrtAsyncClient
    • invokeOperation strips the request-level publishers off the override viastashRequestMetricPublishers and stashes them in a request execution attribute; an interceptor bridges that to the transport's SDK-HTTP attribute.
    • copyObject stashes once at the entry point. copyObject's sub-requests bypass invokeOperation, so a single stash there plus the copy helper's existing override propagation carries the publishers to every sub-request, with no per-sub-request wrapper.
    • Removed the interceptor guard that rejected request-level metric publishers.
  • S3CrtAsyncHttpClient resolves request-level vs client-level publishers (request-level takes precedence) and folds the effective set back into the execution attributes for the response adapter.
  • CopyObjectHelper now propagates the copy request's override configuration to the tagging and annotation sub-requests (GetObjectTagging / PutObjectTagging / annotation reads and writes), which were previously built without it. This was a pre-existing gap: an override set on a copy was silently dropped for those sub-requests, so their CRT telemetry never reached a request-level publisher. All copy sub-requests (head / create / part-copies / complete, and now tagging / annotation) inherit the copy's override.

Testing

  • Unit (S3CrtMetricPublisherResolutionTest): the resolution matrix (none / client-only / request-only / request-overrides-client) and the stash transform (for a normal operation and for copyObject).
  • Integration (S3CrtClientMetricPublisherIntegrationTest): client-level single-part / multipart get, put, and failed paths; request-level getObject and copyObject publish to the request-level publisher while the client-level one is bypassed; and a tagging-directive copy verifies itsGetObjectTagging and PutObjectTagging sub-requests publish to the request-level publisher, exercising the CopyObjectHelper propagation fix end to end.

Screenshots (if appropriate)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the CONTRIBUTING document
  • Local run of mvn install succeeds
  • My code follows the code style of this project
  • My change requires a change to the Javadoc documentation
  • I have updated the Javadoc documentation accordingly
  • I have added tests to cover my changes
  • All new and existing tests passed
  • I have added a changelog entry. Adding a new entry must be accomplished by running the scripts/new-change script and following the instructions. Commit the new file created by the script in .changes/next-release with your changes.
  • My change is to implement 1.11 parity feature and I have updated LaunchChangelog

License

  • I confirm that this pull request can be released under the Apache 2 license

@jencymaryjoseph
jencymaryjoseph requested a review from a team as a code owner August 25, 2026 19:06
@zoewangg
zoewangg self-requested a review August 25, 2026 20:34
* Request-level publishers (if any were set on the request override) take precedence over the client-level
* publishers; otherwise the client-level publishers are used.
*/
@SdkTestInternalApi

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.

Why is it a test internal api?

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.

ah, good catch. These aren't test only apis. Removing all such references.

return operation.apply(stashRequestMetricPublishers(request));
}

@SdkTestInternalApi

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.

Same here and below

long partSizeInBytes = builder.minimalPartSizeInBytes == null ? DEFAULT_PART_SIZE_IN_BYTES :
builder.minimalPartSizeInBytes;
long thresholdInBytes = builder.thresholdInBytes == null ? partSizeInBytes : builder.thresholdInBytes;
this.copyPartSizeInBytes = builder.minimalPartSizeInBytes == null ? DEFAULT_PART_SIZE_IN_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.

Nit: though these fields are only used by copy helper, they are still general partSize and threshold, suggesting keep the original name

* publishers. Used only by {@link #copyObject(CopyObjectRequest)} when the copy request specifies request-level
* publishers, so each copy sub-request publishes CRT telemetry to them instead of to the client-level publishers.
*/
static final class RequestMetricPublisherInjectingClient extends DelegatingS3AsyncClient {

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.

Hope to confirm: this includes head/create/part-copies/complete and tagging/annotation reads and writes, this is the intended design right?


@Test
void failedGetObject_publishesUnsuccessfulApiCallMetrics() throws InterruptedException {
try {

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 use wiremock tests instead for error cases since it's more deterministic?

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.

Added a deterministic wiremock test in S3CrtClientWiremockTest

}
}

@Test

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.

Should we add assertThat(clientPublisher.awaitAtLeast(1, Duration.ofSeconds(1))).isEmpty();?

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.

Good catch, added.

Comment on lines +169 to +173
// The adapter reads its inputs from the execution attributes. METRIC_PUBLISHERS currently holds the
// request-level publishers (if any) the interceptor stashed; resolve them against the client-level publishers
// (request-level takes precedence) and attach the effective set here when there are any. toBuilder() preserves
// everything already in the bag (including CRT_PROGRESS_LISTENER); skip the copy entirely when no publishers
// are configured, to avoid rebuilding the bag on every request.

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.

Are the comments here necessary? Seems pretty straightforward

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.

Agreed, removed

}

private CompletableFuture<List<Tag>> fetchSourceTagging(CopyObjectRequest copyObjectRequest, String sourceVersionId) {
AwsRequestOverrideConfiguration override = copyObjectRequest.overrideConfiguration().orElse(null);

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 add some tests?

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.

Added unit tests in CopyObjectHelperTest verifying the copy's override propagates to every tagging and annotation sub request - getObjectTagging/putObjectTagging and listObjectAnnotations/getObjectAnnotation/putObjectAnnotation.

@jencymaryjoseph
jencymaryjoseph merged commit ab4f33f into feature/master/S3CRTMetricPublisher Aug 27, 2026
5 checks passed
@github-actions

Copy link
Copy Markdown

This pull request has been closed and the conversation has been locked. Comments on closed PRs are hard for our team to see. If you need more assistance, please open a new issue that references this one.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Aug 27, 2026
@jencymaryjoseph
jencymaryjoseph deleted the jencyjos/s3crt/request-metrics branch August 27, 2026 04:04
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants