From d414fecfb04f016df9c3fe62c68edc840917f797 Mon Sep 17 00:00:00 2001 From: Sichan Yoo Date: Tue, 20 Aug 2024 11:11:38 -0700 Subject: [PATCH 1/7] Add BusinessMetrics struct, Context extension for businessMetrics computed property, utility function for grabbing flags from config / context and saving them into context.businessMetrics. --- .../UserAgent/BusinessMetrics.swift | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift new file mode 100644 index 00000000000..f49d5dd24fa --- /dev/null +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift @@ -0,0 +1,106 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import ClientRuntime +import class Smithy.Context +import struct Smithy.AttributeKey + +struct BusinessMetrics { + // Mapping of human readable feature ID to the corresponding metric value + let features: [String: String] + + init( + config: DefaultClientConfiguration & AWSDefaultClientConfiguration, + context: Context + ) { + setFlagsIntoContext(config: config, context: context) + self.features = context.businessMetrics + } +} + +extension BusinessMetrics: CustomStringConvertible { + var description: String { + var commaSeparatedMetricValues = features.values.joined(separator: ",") + // Cut last metric value from string until the + // comma-separated list of metric values are at or below 1024 bytes in size + if commaSeparatedMetricValues.lengthOfBytes(using: .ascii) > 1024 { + while commaSeparatedMetricValues.lengthOfBytes(using: .ascii) > 1024 { + commaSeparatedMetricValues = commaSeparatedMetricValues.substringBeforeLast(",") + } + } + return "m/\(commaSeparatedMetricValues)" + } +} + +private extension String { + func substringBeforeLast(_ separator: String) -> String { + if let range = self.range(of: separator, options: .backwards) { + return String(self[.. { + get { attributes.get(key: businessMetricsKey) ?? [:] } + set(newPair) { + var combined = businessMetrics + combined.merge(newPair) { (current, new) in new } + attributes.set(key: businessMetricsKey, value: combined) + } + } +} + +public let businessMetricsKey = AttributeKey>(name: "BusinessMetrics") + +/* List of readable "feature ID" to "metric value"; last updated on 08/19/2024 + [Feature ID] [Metric Value] + "RESOURCE_MODEL" : "A", + "WAITER" : "B", + "PAGINATOR" : "C", + "RETRY_MODE_LEGACY" : "D", + "RETRY_MODE_STANDARD" : "E", + "RETRY_MODE_ADAPTIVE" : "F", + "S3_TRANSFER" : "G", + "S3_CRYPTO_V1N" : "H", + "S3_CRYPTO_V2" : "I", + "S3_EXPRESS_BUCKET" : "J", + "S3_ACCESS_GRANTS" : "K", + "GZIP_REQUEST_COMPRESSION" : "L", + "PROTOCOL_RPC_V2_CBOR" : "M", + "ENDPOINT_OVERRIDE" : "N", + "ACCOUNT_ID_ENDPOINT" : "O", + "ACCOUNT_ID_MODE_PREFERRED" : "P", + "ACCOUNT_ID_MODE_DISABLED" : "Q", + "ACCOUNT_ID_MODE_REQUIRED" : "R", + "SIGV4A_SIGNING" : "S", + "RESOLVED_ACCOUNT_ID" : "T" + */ +fileprivate func setFlagsIntoContext( + config: DefaultClientConfiguration & AWSDefaultClientConfiguration, + context: Context +) { + // Handle D, E, F + switch config.awsRetryMode { + case .legacy: + context.businessMetrics = ["RETRY_MODE_LEGACY": "D"] + case .standard: + context.businessMetrics = ["RETRY_MODE_STANDARD": "E"] + case .adaptive: + context.businessMetrics = ["RETRY_MODE_ADAPTIVE": "F"] + } + // Handle N + if let endpoint = config.endpoint, !endpoint.isEmpty { + context.businessMetrics = ["ENDPOINT_OVERRIDE": "N"] + } + // Handle S + if context.selectedAuthScheme?.schemeID == "aws.auth#sigv4a" { + context.businessMetrics = ["SIGV4A_SIGNING": "S"] + } +} From 6a90ea16c1762cf0072beda96ca7c26843c7b5c5 Mon Sep 17 00:00:00 2001 From: Sichan Yoo Date: Tue, 20 Aug 2024 11:17:42 -0700 Subject: [PATCH 2/7] Move AWSUserAgentMetadata construction from within generated UserAgentMiddlewrae initialization code, to the runtime UserAgentMiddleware code. This delays user agent struct initialization to right before the request is sent, and is done because we need access to the last-minute values in the context right before the request is sent, in order to grab all features used in request flow and put them in the business metrics section of the user agent. --- .../Middlewares/UserAgentMiddleware.swift | 30 ++++++++++++------- .../codegen/middleware/UserAgentMiddleware.kt | 3 +- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/UserAgentMiddleware.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/UserAgentMiddleware.swift index 79b09f8293c..fe5e0061dec 100644 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/UserAgentMiddleware.swift +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/UserAgentMiddleware.swift @@ -15,14 +15,18 @@ public struct UserAgentMiddleware { private let X_AMZ_USER_AGENT: String = "x-amz-user-agent" private let USER_AGENT: String = "User-Agent" - let metadata: AWSUserAgentMetadata - - public init(metadata: AWSUserAgentMetadata) { - self.metadata = metadata - } - - private func addHeader(builder: HTTPRequestBuilder) { - builder.withHeader(name: USER_AGENT, value: metadata.userAgent) + let serviceID: String + let version: String + let config: DefaultClientConfiguration & AWSDefaultClientConfiguration + + public init( + serviceID: String, + version: String, + config: DefaultClientConfiguration & AWSDefaultClientConfiguration + ) { + self.serviceID = serviceID + self.version = version + self.config = config } } @@ -30,9 +34,15 @@ extension UserAgentMiddleware: HttpInterceptor { public typealias InputType = OperationStackInput public typealias OutputType = OperationStackOutput - public func modifyBeforeRetryLoop(context: some MutableRequest) async throws { + public func modifyBeforeTransmit(context: some MutableRequest) async throws { + let awsUserAgentString = AWSUserAgentMetadata.fromConfigAndContext( + serviceID: serviceID, + version: version, + config: config, + context: context.getAttributes() + ).userAgent let builder = context.getRequest().toBuilder() - addHeader(builder: builder) + builder.withHeader(name: USER_AGENT, value: awsUserAgentString) context.updateRequest(updated: builder.build()) } } diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/middleware/UserAgentMiddleware.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/middleware/UserAgentMiddleware.kt index e1a29a24d2f..3360068fc9f 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/middleware/UserAgentMiddleware.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/middleware/UserAgentMiddleware.kt @@ -35,8 +35,7 @@ class UserAgentMiddleware(val settings: SwiftSettings) : MiddlewareRenderable { private fun middlewareParamsString(writer: SwiftWriter): String { return writer.format( - "metadata: \$N.fromConfig(serviceID: serviceName, version: \$S, config: config)", - AWSClientRuntimeTypes.Core.AWSUserAgentMetadata, + "serviceID: serviceName, version: \$S, config: config", settings.moduleVersion, ) } From 82bf9acc616ef923f0733e5a06878d925685c342 Mon Sep 17 00:00:00 2001 From: Sichan Yoo Date: Tue, 20 Aug 2024 11:19:00 -0700 Subject: [PATCH 3/7] Replace config metadata and feature metadata with business metrics. Refactor fromConfig() initializer wrapper to fromConfigAndContext(), now that business metrics needs values saved in context as well. --- .../UserAgent/AWSUserAgentMetadata.swift | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/AWSUserAgentMetadata.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/AWSUserAgentMetadata.swift index 2a23ac147d5..c8afa9e67aa 100644 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/AWSUserAgentMetadata.swift +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/AWSUserAgentMetadata.swift @@ -6,6 +6,7 @@ // import ClientRuntime +import class Smithy.Context public struct AWSUserAgentMetadata { let sdkMetadata: SDKMetadata @@ -15,9 +16,8 @@ public struct AWSUserAgentMetadata { let osMetadata: OSMetadata let languageMetadata: LanguageMetadata let executionEnvMetadata: ExecutionEnvMetadata? - let configMetadata: [ConfigMetadata] + let businessMetrics: BusinessMetrics? let appIDMetadata: AppIDMetadata? - let featureMetadata: [FeatureMetadata] let frameworkMetadata: [FrameworkMetadata] /// ABNF for the user agent: @@ -29,9 +29,8 @@ public struct AWSUserAgentMetadata { /// language-metadata RWS /// [env-metadata RWS] /// ; ordering is not strictly required in the following section - /// *(config-metadata RWS) + /// [business-metrics] /// [appId] - /// *(feat-metadata RWS) /// *(framework-metadata RWS) var userAgent: String { return [ @@ -42,9 +41,8 @@ public struct AWSUserAgentMetadata { [osMetadata.description], [languageMetadata.description], [executionEnvMetadata?.description], - configMetadata.map(\.description) as [String?], + [businessMetrics?.description], [appIDMetadata?.description], - featureMetadata.map(\.description) as [String?], frameworkMetadata.map(\.description) as [String?] ].flatMap { $0 }.compactMap { $0 }.joined(separator: " ") } @@ -56,9 +54,8 @@ public struct AWSUserAgentMetadata { osMetadata: OSMetadata, languageMetadata: LanguageMetadata, executionEnvMetadata: ExecutionEnvMetadata? = nil, - configMetadata: [ConfigMetadata] = [], + businessMetrics: BusinessMetrics? = nil, appIDMetadata: AppIDMetadata? = nil, - featureMetadata: [FeatureMetadata] = [], frameworkMetadata: [FrameworkMetadata] = [] ) { self.sdkMetadata = sdkMetadata @@ -67,16 +64,16 @@ public struct AWSUserAgentMetadata { self.osMetadata = osMetadata self.languageMetadata = languageMetadata self.executionEnvMetadata = executionEnvMetadata - self.configMetadata = configMetadata + self.businessMetrics = businessMetrics self.appIDMetadata = appIDMetadata - self.featureMetadata = featureMetadata self.frameworkMetadata = frameworkMetadata } - public static func fromConfig( + public static func fromConfigAndContext( serviceID: String, version: String, - config: DefaultClientConfiguration & AWSDefaultClientConfiguration + config: DefaultClientConfiguration & AWSDefaultClientConfiguration, + context: Context ) -> AWSUserAgentMetadata { let apiMetadata = APIMetadata(serviceID: serviceID, version: version) let sdkMetadata = SDKMetadata(version: apiMetadata.version) @@ -85,7 +82,7 @@ public struct AWSUserAgentMetadata { let osVersion = PlatformOperationSystemVersion.operatingSystemVersion() let osMetadata = OSMetadata(family: currentOS, version: osVersion) let languageMetadata = LanguageMetadata(version: swiftVersion) - let configMetadata = [ConfigMetadata(type: .retry(config.awsRetryMode))] + let businessMetrics = BusinessMetrics(config: config, context: context) let appIDMetadata = AppIDMetadata(name: config.appID) let frameworkMetadata = [FrameworkMetadata]() return AWSUserAgentMetadata( @@ -95,9 +92,8 @@ public struct AWSUserAgentMetadata { osMetadata: osMetadata, languageMetadata: languageMetadata, executionEnvMetadata: ExecutionEnvMetadata.detectExecEnv(), - configMetadata: configMetadata, + businessMetrics: businessMetrics, appIDMetadata: appIDMetadata, - featureMetadata: [], // Feature metadata will be supplied when features are implemented frameworkMetadata: frameworkMetadata ) } From 013064d011e4cdc170300efb7f699538c9c4b239 Mon Sep 17 00:00:00 2001 From: Sichan Yoo Date: Tue, 20 Aug 2024 11:19:26 -0700 Subject: [PATCH 4/7] Remove config metadata and feature metadata code and tests. --- .../UserAgent/ConfigMetadata.swift | 24 --------------- .../UserAgent/FeatureMetadata.swift | 29 ------------------- .../UserAgent/ConfigMetadataTests.swift | 27 ----------------- .../UserAgent/FeatureMetadataTests.swift | 27 ----------------- 4 files changed, 107 deletions(-) delete mode 100644 Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/ConfigMetadata.swift delete mode 100644 Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/FeatureMetadata.swift delete mode 100644 Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/ConfigMetadataTests.swift delete mode 100644 Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/FeatureMetadataTests.swift diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/ConfigMetadata.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/ConfigMetadata.swift deleted file mode 100644 index 999d242299f..00000000000 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/ConfigMetadata.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -struct ConfigMetadata { - let type: ConfigMetadataType -} - -extension ConfigMetadata: CustomStringConvertible { - - var description: String { - switch type { - case .retry(let mode): - return "cfg/retry-mode#\(mode.rawValue.userAgentToken)" - } - } -} - -enum ConfigMetadataType { - case retry(AWSRetryMode) -} diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/FeatureMetadata.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/FeatureMetadata.swift deleted file mode 100644 index 2086df90b57..00000000000 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/FeatureMetadata.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -struct FeatureMetadata { - let feature: String - let version: String? - let additionalMetadata: [AdditionalMetadata] - - init(feature: String, version: String? = nil, additionalMetadata: [AdditionalMetadata] = []) { - self.feature = feature - self.version = version - self.additionalMetadata = additionalMetadata - } - } - -extension FeatureMetadata: CustomStringConvertible { - - var description: String { - var description = "ft/\(feature.userAgentToken)" - if let version = version, !version.isEmpty { - description += "#\(version.userAgentToken)" - } - return description - } -} diff --git a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/ConfigMetadataTests.swift b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/ConfigMetadataTests.swift deleted file mode 100644 index b78601f2e2d..00000000000 --- a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/ConfigMetadataTests.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -@testable import AWSClientRuntime -import XCTest - -class ConfigMetadataTests: XCTestCase { - - func test_description_returnsCorrectMetadataForRetry() { - let subject = ConfigMetadata(type: .retry(.legacy)) - XCTAssertEqual(subject.description, "cfg/retry-mode#legacy") - } - - func test_description_returnsCorrectMetadataForStandard() { - let subject = ConfigMetadata(type: .retry(.standard)) - XCTAssertEqual(subject.description, "cfg/retry-mode#standard") - } - - func test_description_returnsCorrectMetadataForAdaptive() { - let subject = ConfigMetadata(type: .retry(.adaptive)) - XCTAssertEqual(subject.description, "cfg/retry-mode#adaptive") - } -} diff --git a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/FeatureMetadataTests.swift b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/FeatureMetadataTests.swift deleted file mode 100644 index 227ee6cf540..00000000000 --- a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/FeatureMetadataTests.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -@testable import AWSClientRuntime -import XCTest - -class FeatureMetadataTests: XCTestCase { - - func test_description_sanitizedFeatureNameAndVersion() { - let subject = FeatureMetadata(feature: "🤡 Car", version: "7.8.🤡") - XCTAssertEqual(subject.description, "ft/--Car#7.8.-") - } - - func test_description_omitsVersionIfVersionIsOmitted() { - let subject = FeatureMetadata(feature: "🤡 Car") - XCTAssertEqual(subject.description, "ft/--Car") - } - - func test_description_omitsVersionIfVersionIsEmptyString() { - let subject = FeatureMetadata(feature: "🤡 Car", version: "") - XCTAssertEqual(subject.description, "ft/--Car") - } -} From c01aa9c686640d4530dae5f746150319ad12f51b Mon Sep 17 00:00:00 2001 From: Sichan Yoo Date: Tue, 20 Aug 2024 14:04:31 -0700 Subject: [PATCH 5/7] Add UserAgetnValuesFromConfig class to act as container for subset of relevant values from config. Add unit tests for BusinessMetrics. --- .../Middlewares/UserAgentMiddleware.swift | 2 +- .../UserAgent/AWSUserAgentMetadata.swift | 20 ++++++- .../UserAgent/BusinessMetrics.swift | 6 +- .../UserAgent/BusinessMetricsTests.swift | 60 +++++++++++++++++++ 4 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/BusinessMetricsTests.swift diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/UserAgentMiddleware.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/UserAgentMiddleware.swift index fe5e0061dec..ea02c9fb76d 100644 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/UserAgentMiddleware.swift +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/UserAgentMiddleware.swift @@ -38,7 +38,7 @@ extension UserAgentMiddleware: HttpInterceptor { let awsUserAgentString = AWSUserAgentMetadata.fromConfigAndContext( serviceID: serviceID, version: version, - config: config, + config: UserAgentValuesFromConfig(config: config), context: context.getAttributes() ).userAgent let builder = context.getRequest().toBuilder() diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/AWSUserAgentMetadata.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/AWSUserAgentMetadata.swift index c8afa9e67aa..1a178ccc1f2 100644 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/AWSUserAgentMetadata.swift +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/AWSUserAgentMetadata.swift @@ -72,7 +72,7 @@ public struct AWSUserAgentMetadata { public static func fromConfigAndContext( serviceID: String, version: String, - config: DefaultClientConfiguration & AWSDefaultClientConfiguration, + config: UserAgentValuesFromConfig, context: Context ) -> AWSUserAgentMetadata { let apiMetadata = APIMetadata(serviceID: serviceID, version: version) @@ -98,3 +98,21 @@ public struct AWSUserAgentMetadata { ) } } + +public class UserAgentValuesFromConfig { + var appID: String? + var endpoint: String? + var awsRetryMode: AWSRetryMode + + public init(appID: String?, endpoint: String?, awsRetryMode: AWSRetryMode) { + self.endpoint = endpoint + self.awsRetryMode = awsRetryMode + self.appID = appID + } + + public init(config: DefaultClientConfiguration & AWSDefaultClientConfiguration) { + self.appID = config.appID + self.endpoint = config.endpoint + self.awsRetryMode = config.awsRetryMode + } +} diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift index f49d5dd24fa..5bbc61b16ed 100644 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift @@ -14,7 +14,7 @@ struct BusinessMetrics { let features: [String: String] init( - config: DefaultClientConfiguration & AWSDefaultClientConfiguration, + config: UserAgentValuesFromConfig, context: Context ) { setFlagsIntoContext(config: config, context: context) @@ -24,7 +24,7 @@ struct BusinessMetrics { extension BusinessMetrics: CustomStringConvertible { var description: String { - var commaSeparatedMetricValues = features.values.joined(separator: ",") + var commaSeparatedMetricValues = features.values.sorted().joined(separator: ",") // Cut last metric value from string until the // comma-separated list of metric values are at or below 1024 bytes in size if commaSeparatedMetricValues.lengthOfBytes(using: .ascii) > 1024 { @@ -83,7 +83,7 @@ public let businessMetricsKey = AttributeKey>(name: " "RESOLVED_ACCOUNT_ID" : "T" */ fileprivate func setFlagsIntoContext( - config: DefaultClientConfiguration & AWSDefaultClientConfiguration, + config: UserAgentValuesFromConfig, context: Context ) { // Handle D, E, F diff --git a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/BusinessMetricsTests.swift b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/BusinessMetricsTests.swift new file mode 100644 index 00000000000..672db12cadb --- /dev/null +++ b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/BusinessMetricsTests.swift @@ -0,0 +1,60 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import ClientRuntime +@testable import AWSClientRuntime +import SmithyRetriesAPI +import SmithyHTTPAuthAPI +import SmithyIdentity +import SmithyRetriesAPI +import Smithy + +class BusinessMetricsTests: XCTestCase { + var context: Context! + + override func setUp() async throws { + context = Context(attributes: Attributes()) + } + + func test_business_metrics_section_truncation() { + context.businessMetrics = ["SHORT_FILLER": "A"] + let longMetricValue = String(repeating: "F", count: 1025) + context.businessMetrics = ["LONG_FILLER": longMetricValue] + let userAgent = AWSUserAgentMetadata.fromConfigAndContext( + serviceID: "test", + version: "1.0", + config: UserAgentValuesFromConfig(appID: nil, endpoint: nil, awsRetryMode: .standard), + context: context + ) + // Assert values in context match with values assigned to user agent + XCTAssertEqual(userAgent.businessMetrics?.features, context.businessMetrics) + // Assert string gets truncated successfully + let expectedTruncatedString = "m/A,E" + XCTAssertEqual(userAgent.businessMetrics?.description, expectedTruncatedString) + } + + func test_multiple_flags_in_context() { + context.businessMetrics = ["FIRST": "A"] + context.businessMetrics = ["SECOND": "B"] + context.setSelectedAuthScheme(SelectedAuthScheme( // S + schemeID: "aws.auth#sigv4a", + identity: nil, + signingProperties: nil, + signer: nil + )) + let userAgent = AWSUserAgentMetadata.fromConfigAndContext( + serviceID: "test", + version: "1.0", + config: UserAgentValuesFromConfig(appID: nil, endpoint: "test-endpoint", awsRetryMode: .adaptive), + context: context + ) + // F comes from retry mode being adaptive & N comes from endpoint override + let expectedString = "m/A,B,F,N,S" + XCTAssertEqual(userAgent.businessMetrics?.description, expectedString) + } +} From e9e88a63d90da164a66939d64cd1cd509f58c657 Mon Sep 17 00:00:00 2001 From: Sichan Yoo Date: Wed, 21 Aug 2024 10:14:15 -0700 Subject: [PATCH 6/7] Update codegen tests with new UserAgentMiddleware init params --- .../smithy/aws/swift/codegen/PresignerGeneratorTests.kt | 8 ++++---- .../swift/codegen/awsquery/AWSQueryOperationStackTest.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt index ddac8dea3d7..0503a753b5d 100644 --- a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt +++ b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt @@ -56,7 +56,7 @@ extension GetFooInput { builder.applySigner(ClientRuntime.SignerMiddleware()) let endpointParams = EndpointParams() builder.applyEndpoint(AWSClientRuntime.EndpointResolverMiddleware(endpointResolverBlock: { [config] in try config.endpointResolver.resolve(params: ${'$'}0) }, endpointParams: endpointParams)) - builder.interceptors.add(AWSClientRuntime.UserAgentMiddleware(metadata: AWSClientRuntime.AWSUserAgentMetadata.fromConfig(serviceID: serviceName, version: "1.0.0", config: config))) + builder.interceptors.add(AWSClientRuntime.UserAgentMiddleware(serviceID: serviceName, version: "1.0.0", config: config)) builder.selectAuthScheme(ClientRuntime.AuthSchemeMiddleware()) var metricsAttributes = Smithy.Attributes() metricsAttributes.set(key: ClientRuntime.OrchestratorMetricsAttributesKeys.service, value: "Example") @@ -129,7 +129,7 @@ extension PostFooInput { builder.applySigner(ClientRuntime.SignerMiddleware()) let endpointParams = EndpointParams() builder.applyEndpoint(AWSClientRuntime.EndpointResolverMiddleware(endpointResolverBlock: { [config] in try config.endpointResolver.resolve(params: ${'$'}0) }, endpointParams: endpointParams)) - builder.interceptors.add(AWSClientRuntime.UserAgentMiddleware(metadata: AWSClientRuntime.AWSUserAgentMetadata.fromConfig(serviceID: serviceName, version: "1.0.0", config: config))) + builder.interceptors.add(AWSClientRuntime.UserAgentMiddleware(serviceID: serviceName, version: "1.0.0", config: config)) builder.selectAuthScheme(ClientRuntime.AuthSchemeMiddleware()) var metricsAttributes = Smithy.Attributes() metricsAttributes.set(key: ClientRuntime.OrchestratorMetricsAttributesKeys.service, value: "Example") @@ -202,7 +202,7 @@ extension PutFooInput { builder.applySigner(ClientRuntime.SignerMiddleware()) let endpointParams = EndpointParams() builder.applyEndpoint(AWSClientRuntime.EndpointResolverMiddleware(endpointResolverBlock: { [config] in try config.endpointResolver.resolve(params: ${'$'}0) }, endpointParams: endpointParams)) - builder.interceptors.add(AWSClientRuntime.UserAgentMiddleware(metadata: AWSClientRuntime.AWSUserAgentMetadata.fromConfig(serviceID: serviceName, version: "1.0.0", config: config))) + builder.interceptors.add(AWSClientRuntime.UserAgentMiddleware(serviceID: serviceName, version: "1.0.0", config: config)) builder.selectAuthScheme(ClientRuntime.AuthSchemeMiddleware()) var metricsAttributes = Smithy.Attributes() metricsAttributes.set(key: ClientRuntime.OrchestratorMetricsAttributesKeys.service, value: "Example") @@ -276,7 +276,7 @@ extension PutObjectInput { let endpointParams = EndpointParams() context.attributes.set(key: Smithy.AttributeKey(name: "EndpointParams"), value: endpointParams) builder.applyEndpoint(AWSClientRuntime.EndpointResolverMiddleware(endpointResolverBlock: { [config] in try config.endpointResolver.resolve(params: ${'$'}0) }, endpointParams: endpointParams)) - builder.interceptors.add(AWSClientRuntime.UserAgentMiddleware(metadata: AWSClientRuntime.AWSUserAgentMetadata.fromConfig(serviceID: serviceName, version: "1.0.0", config: config))) + builder.interceptors.add(AWSClientRuntime.UserAgentMiddleware(serviceID: serviceName, version: "1.0.0", config: config)) builder.selectAuthScheme(ClientRuntime.AuthSchemeMiddleware()) var metricsAttributes = Smithy.Attributes() metricsAttributes.set(key: ClientRuntime.OrchestratorMetricsAttributesKeys.service, value: "S3") diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsquery/AWSQueryOperationStackTest.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsquery/AWSQueryOperationStackTest.kt index 71f1a00e99c..d8c49ae8680 100644 --- a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsquery/AWSQueryOperationStackTest.kt +++ b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsquery/AWSQueryOperationStackTest.kt @@ -39,7 +39,7 @@ class AWSQueryOperationStackTest { builder.applySigner(ClientRuntime.SignerMiddleware()) let endpointParams = EndpointParams() builder.applyEndpoint(AWSClientRuntime.EndpointResolverMiddleware(endpointResolverBlock: { [config] in try config.endpointResolver.resolve(params: ${'$'}0) }, endpointParams: endpointParams)) - builder.interceptors.add(AWSClientRuntime.UserAgentMiddleware(metadata: AWSClientRuntime.AWSUserAgentMetadata.fromConfig(serviceID: serviceName, version: "1.0.0", config: config))) + builder.interceptors.add(AWSClientRuntime.UserAgentMiddleware(serviceID: serviceName, version: "1.0.0", config: config)) builder.serialize(ClientRuntime.BodyMiddleware(rootNodeInfo: "", inputWritingClosure: NoInputAndOutputInput.write(value:to:))) builder.interceptors.add(ClientRuntime.ContentTypeMiddleware(contentType: "application/x-www-form-urlencoded")) builder.selectAuthScheme(ClientRuntime.AuthSchemeMiddleware()) From 786594588199229110f580e69b2235735363f0a0 Mon Sep 17 00:00:00 2001 From: Sichan Yoo Date: Wed, 21 Aug 2024 10:52:41 -0700 Subject: [PATCH 7/7] Refine comment for progress tracking in future --- .../UserAgent/BusinessMetrics.swift | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift index 5bbc61b16ed..6c818eee361 100644 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/UserAgent/BusinessMetrics.swift @@ -60,27 +60,27 @@ public extension Context { public let businessMetricsKey = AttributeKey>(name: "BusinessMetrics") /* List of readable "feature ID" to "metric value"; last updated on 08/19/2024 - [Feature ID] [Metric Value] - "RESOURCE_MODEL" : "A", - "WAITER" : "B", - "PAGINATOR" : "C", - "RETRY_MODE_LEGACY" : "D", - "RETRY_MODE_STANDARD" : "E", - "RETRY_MODE_ADAPTIVE" : "F", - "S3_TRANSFER" : "G", - "S3_CRYPTO_V1N" : "H", - "S3_CRYPTO_V2" : "I", - "S3_EXPRESS_BUCKET" : "J", - "S3_ACCESS_GRANTS" : "K", - "GZIP_REQUEST_COMPRESSION" : "L", - "PROTOCOL_RPC_V2_CBOR" : "M", - "ENDPOINT_OVERRIDE" : "N", - "ACCOUNT_ID_ENDPOINT" : "O", - "ACCOUNT_ID_MODE_PREFERRED" : "P", - "ACCOUNT_ID_MODE_DISABLED" : "Q", - "ACCOUNT_ID_MODE_REQUIRED" : "R", - "SIGV4A_SIGNING" : "S", - "RESOLVED_ACCOUNT_ID" : "T" + [Feature ID] [Metric Value] [Flag Supported] + "RESOURCE_MODEL" : "A" : + "WAITER" : "B" : + "PAGINATOR" : "C" : + "RETRY_MODE_LEGACY" : "D" : Y + "RETRY_MODE_STANDARD" : "E" : Y + "RETRY_MODE_ADAPTIVE" : "F" : Y + "S3_TRANSFER" : "G" : + "S3_CRYPTO_V1N" : "H" : + "S3_CRYPTO_V2" : "I" : + "S3_EXPRESS_BUCKET" : "J" : + "S3_ACCESS_GRANTS" : "K" : + "GZIP_REQUEST_COMPRESSION" : "L" : + "PROTOCOL_RPC_V2_CBOR" : "M" : + "ENDPOINT_OVERRIDE" : "N" : Y + "ACCOUNT_ID_ENDPOINT" : "O" : + "ACCOUNT_ID_MODE_PREFERRED" : "P" : + "ACCOUNT_ID_MODE_DISABLED" : "Q" : + "ACCOUNT_ID_MODE_REQUIRED" : "R" : + "SIGV4A_SIGNING" : "S" : Y + "RESOLVED_ACCOUNT_ID" : "T" : */ fileprivate func setFlagsIntoContext( config: UserAgentValuesFromConfig,