1use crate::sdk_feature::AwsSdkFeature;
7use aws_credential_types::credential_feature::AwsCredentialFeature;
8use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature;
9use std::borrow::Cow;
10use std::collections::HashMap;
11use std::fmt;
12use std::sync::LazyLock;
13
14const MAX_COMMA_SEPARATED_METRICS_VALUES_LENGTH: usize = 1024;
15#[allow(dead_code)]
16const MAX_METRICS_ID_NUMBER: usize = 350;
17
18macro_rules! iterable_enum {
19 ($docs:tt, $enum_name:ident, $( $variant:ident ),*) => {
20 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
21 #[non_exhaustive]
22 #[doc = $docs]
23 #[allow(missing_docs)] pub enum $enum_name {
25 $( $variant ),*
26 }
27
28 #[allow(dead_code)]
29 impl $enum_name {
30 pub(crate) fn iter() -> impl Iterator<Item = &'static $enum_name> {
31 const VARIANTS: &[$enum_name] = &[
32 $( $enum_name::$variant ),*
33 ];
34 VARIANTS.iter()
35 }
36 }
37 };
38}
39
40struct Base64Iterator {
41 current: Vec<usize>,
42 base64_chars: Vec<char>,
43}
44
45impl Base64Iterator {
46 #[allow(dead_code)]
47 fn new() -> Self {
48 Base64Iterator {
49 current: vec![0], base64_chars: (b'A'..=b'Z') .chain(b'a'..=b'z') .chain(b'0'..=b'9') .chain([b'+', b'-']) .map(|c| c as char)
55 .collect(),
56 }
57 }
58
59 fn increment(&mut self) {
60 let mut i = 0;
61 while i < self.current.len() {
62 self.current[i] += 1;
63 if self.current[i] < self.base64_chars.len() {
64 return;
66 }
67 self.current[i] = 0;
68 i += 1;
69 }
70 self.current.push(0); }
72}
73
74impl Iterator for Base64Iterator {
75 type Item = String;
76
77 fn next(&mut self) -> Option<Self::Item> {
78 if self.current.is_empty() {
79 return None; }
81
82 let result: String = self
84 .current
85 .iter()
86 .rev()
87 .map(|&idx| self.base64_chars[idx])
88 .collect();
89
90 self.increment();
92 Some(result)
93 }
94}
95
96pub(super) static FEATURE_ID_TO_METRIC_VALUE: LazyLock<HashMap<BusinessMetric, Cow<'static, str>>> =
97 LazyLock::new(|| {
98 let mut m = HashMap::new();
99 for (metric, value) in BusinessMetric::iter()
100 .cloned()
101 .zip(Base64Iterator::new())
102 .take(MAX_METRICS_ID_NUMBER)
103 {
104 m.insert(metric, Cow::Owned(value));
105 }
106 m
107 });
108
109iterable_enum!(
110 "Enumerates human readable identifiers for the features tracked by metrics",
111 BusinessMetric,
112 ResourceModel,
113 Waiter,
114 Paginator,
115 RetryModeLegacy,
116 RetryModeStandard,
117 RetryModeAdaptive,
118 S3Transfer,
119 S3CryptoV1n,
120 S3CryptoV2,
121 S3ExpressBucket,
122 S3AccessGrants,
123 GzipRequestCompression,
124 ProtocolRpcV2Cbor,
125 EndpointOverride,
126 AccountIdEndpoint,
127 AccountIdModePreferred,
128 AccountIdModeDisabled,
129 AccountIdModeRequired,
130 Sigv4aSigning,
131 ResolvedAccountId,
132 FlexibleChecksumsReqCrc32,
133 FlexibleChecksumsReqCrc32c,
134 FlexibleChecksumsReqCrc64,
135 FlexibleChecksumsReqSha1,
136 FlexibleChecksumsReqSha256,
137 FlexibleChecksumsReqWhenSupported,
138 FlexibleChecksumsReqWhenRequired,
139 FlexibleChecksumsResWhenSupported,
140 FlexibleChecksumsResWhenRequired,
141 DdbMapper,
142 CredentialsCode,
143 CredentialsJvmSystemProperties,
144 CredentialsEnvVars,
145 CredentialsEnvVarsStsWebIdToken,
146 CredentialsStsAssumeRole,
147 CredentialsStsAssumeRoleSaml,
148 CredentialsStsAssumeRoleWebId,
149 CredentialsStsFederationToken,
150 CredentialsStsSessionToken,
151 CredentialsProfile,
152 CredentialsProfileSourceProfile,
153 CredentialsProfileNamedProvider,
154 CredentialsProfileStsWebIdToken,
155 CredentialsProfileSso,
156 CredentialsSso,
157 CredentialsProfileSsoLegacy,
158 CredentialsSsoLegacy,
159 CredentialsProfileProcess,
160 CredentialsProcess,
161 CredentialsBoto2ConfigFile,
162 CredentialsAwsSdkStore,
163 CredentialsHttp,
164 CredentialsImds,
165 SsoLoginDevice,
166 SsoLoginAuth,
167 BearerServiceEnvVars,
168 ObservabilityTracing,
169 ObservabilityMetrics,
170 ObservabilityOtelTracing,
171 ObservabilityOtelMetrics,
172 CredentialsCognito,
173 S3TransferUploadDirectory,
174 S3TransferDownloadDirectory,
175 CliV1ToV2MigrationDebugMode,
176 LoginSameDevice,
177 LoginCrossDevice,
178 CredentialsProfileLogin,
179 CredentialsLogin
180);
181
182pub(crate) trait ProvideBusinessMetric {
183 fn provide_business_metric(&self) -> Option<BusinessMetric>;
184}
185
186impl ProvideBusinessMetric for SmithySdkFeature {
187 fn provide_business_metric(&self) -> Option<BusinessMetric> {
188 use SmithySdkFeature::*;
189 match self {
190 Waiter => Some(BusinessMetric::Waiter),
191 Paginator => Some(BusinessMetric::Paginator),
192 GzipRequestCompression => Some(BusinessMetric::GzipRequestCompression),
193 ProtocolRpcV2Cbor => Some(BusinessMetric::ProtocolRpcV2Cbor),
194 RetryModeStandard => Some(BusinessMetric::RetryModeStandard),
195 RetryModeAdaptive => Some(BusinessMetric::RetryModeAdaptive),
196 FlexibleChecksumsReqCrc32 => Some(BusinessMetric::FlexibleChecksumsReqCrc32),
197 FlexibleChecksumsReqCrc32c => Some(BusinessMetric::FlexibleChecksumsReqCrc32c),
198 FlexibleChecksumsReqCrc64 => Some(BusinessMetric::FlexibleChecksumsReqCrc64),
199 FlexibleChecksumsReqSha1 => Some(BusinessMetric::FlexibleChecksumsReqSha1),
200 FlexibleChecksumsReqSha256 => Some(BusinessMetric::FlexibleChecksumsReqSha256),
201 FlexibleChecksumsReqWhenSupported => {
202 Some(BusinessMetric::FlexibleChecksumsReqWhenSupported)
203 }
204 FlexibleChecksumsReqWhenRequired => {
205 Some(BusinessMetric::FlexibleChecksumsReqWhenRequired)
206 }
207 FlexibleChecksumsResWhenSupported => {
208 Some(BusinessMetric::FlexibleChecksumsResWhenSupported)
209 }
210 FlexibleChecksumsResWhenRequired => {
211 Some(BusinessMetric::FlexibleChecksumsResWhenRequired)
212 }
213 otherwise => {
214 tracing::warn!(
218 "Attempted to provide `BusinessMetric` for `{otherwise:?}`, which is not recognized in the current version of the `aws-runtime` crate. \
219 Consider upgrading to the latest version to ensure that all tracked features are properly reported in your metrics."
220 );
221 None
222 }
223 }
224 }
225}
226
227impl ProvideBusinessMetric for AwsSdkFeature {
228 fn provide_business_metric(&self) -> Option<BusinessMetric> {
229 use AwsSdkFeature::*;
230 match self {
231 AccountIdModePreferred => Some(BusinessMetric::AccountIdModePreferred),
232 AccountIdModeDisabled => Some(BusinessMetric::AccountIdModeDisabled),
233 AccountIdModeRequired => Some(BusinessMetric::AccountIdModeRequired),
234 S3Transfer => Some(BusinessMetric::S3Transfer),
235 SsoLoginDevice => Some(BusinessMetric::SsoLoginDevice),
236 SsoLoginAuth => Some(BusinessMetric::SsoLoginAuth),
237 }
238 }
239}
240
241impl ProvideBusinessMetric for AwsCredentialFeature {
242 fn provide_business_metric(&self) -> Option<BusinessMetric> {
243 use AwsCredentialFeature::*;
244 match self {
245 ResolvedAccountId => Some(BusinessMetric::ResolvedAccountId),
246 CredentialsCode => Some(BusinessMetric::CredentialsCode),
247 CredentialsEnvVars => Some(BusinessMetric::CredentialsEnvVars),
248 CredentialsEnvVarsStsWebIdToken => {
249 Some(BusinessMetric::CredentialsEnvVarsStsWebIdToken)
250 }
251 CredentialsStsAssumeRole => Some(BusinessMetric::CredentialsStsAssumeRole),
252 CredentialsStsAssumeRoleSaml => Some(BusinessMetric::CredentialsStsAssumeRoleSaml),
253 CredentialsStsAssumeRoleWebId => Some(BusinessMetric::CredentialsStsAssumeRoleWebId),
254 CredentialsStsFederationToken => Some(BusinessMetric::CredentialsStsFederationToken),
255 CredentialsStsSessionToken => Some(BusinessMetric::CredentialsStsSessionToken),
256 CredentialsProfile => Some(BusinessMetric::CredentialsProfile),
257 CredentialsProfileSourceProfile => {
258 Some(BusinessMetric::CredentialsProfileSourceProfile)
259 }
260 CredentialsProfileNamedProvider => {
261 Some(BusinessMetric::CredentialsProfileNamedProvider)
262 }
263 CredentialsProfileStsWebIdToken => {
264 Some(BusinessMetric::CredentialsProfileStsWebIdToken)
265 }
266 CredentialsProfileSso => Some(BusinessMetric::CredentialsProfileSso),
267 CredentialsSso => Some(BusinessMetric::CredentialsSso),
268 CredentialsProfileProcess => Some(BusinessMetric::CredentialsProfileProcess),
269 CredentialsProcess => Some(BusinessMetric::CredentialsProcess),
270 CredentialsHttp => Some(BusinessMetric::CredentialsHttp),
271 CredentialsImds => Some(BusinessMetric::CredentialsImds),
272 BearerServiceEnvVars => Some(BusinessMetric::BearerServiceEnvVars),
273 S3ExpressBucket => Some(BusinessMetric::S3ExpressBucket),
274 CredentialsProfileLogin => Some(BusinessMetric::CredentialsProfileLogin),
275 CredentialsLogin => Some(BusinessMetric::CredentialsLogin),
276 otherwise => {
277 tracing::warn!(
281 "Attempted to provide `BusinessMetric` for `{otherwise:?}`, which is not recognized in the current version of the `aws-runtime` crate. \
282 Consider upgrading to the latest version to ensure that all tracked features are properly reported in your metrics."
283 );
284 None
285 }
286 }
287 }
288}
289
290#[derive(Clone, Debug, Default)]
291pub(super) struct BusinessMetrics(Vec<BusinessMetric>);
292
293impl BusinessMetrics {
294 pub(super) fn push(&mut self, metric: BusinessMetric) {
295 self.0.push(metric);
296 }
297
298 pub(super) fn is_empty(&self) -> bool {
299 self.0.is_empty()
300 }
301}
302
303fn drop_unfinished_metrics_to_fit(csv: &str, max_len: usize) -> Cow<'_, str> {
304 if csv.len() <= max_len {
305 Cow::Borrowed(csv)
306 } else {
307 let truncated = &csv[..max_len];
308 if let Some(pos) = truncated.rfind(',') {
309 Cow::Owned(truncated[..pos].to_owned())
310 } else {
311 Cow::Owned(truncated.to_owned())
312 }
313 }
314}
315
316impl fmt::Display for BusinessMetrics {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 let metrics_values = self
320 .0
321 .iter()
322 .map(|feature_id| {
323 FEATURE_ID_TO_METRIC_VALUE
324 .get(feature_id)
325 .expect("{feature_id:?} should be found in `FEATURE_ID_TO_METRIC_VALUE`")
326 .clone()
327 })
328 .collect::<Vec<_>>()
329 .join(",");
330
331 let metrics_values = drop_unfinished_metrics_to_fit(
332 &metrics_values,
333 MAX_COMMA_SEPARATED_METRICS_VALUES_LENGTH,
334 );
335
336 write!(f, "m/{metrics_values}")
337 }
338}
339#[cfg(test)]
340mod tests {
341 use crate::user_agent::metrics::{
342 drop_unfinished_metrics_to_fit, Base64Iterator, FEATURE_ID_TO_METRIC_VALUE,
343 MAX_METRICS_ID_NUMBER,
344 };
345 use crate::user_agent::BusinessMetric;
346 use convert_case::{Boundary, Case, Casing};
347 use std::collections::HashMap;
348 use std::fmt::{Display, Formatter};
349
350 impl Display for BusinessMetric {
351 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
352 f.write_str(
353 &format!("{:?}", self)
354 .as_str()
355 .from_case(Case::Pascal)
356 .with_boundaries(&[Boundary::DigitUpper, Boundary::LowerUpper])
357 .to_case(Case::ScreamingSnake),
358 )
359 }
360 }
361
362 #[test]
363 fn feature_id_to_metric_value() {
364 const EXPECTED: &str = include_str!("test_data/feature_id_to_metric_value.json");
365
366 let expected: HashMap<&str, &str> = serde_json::from_str(EXPECTED).unwrap();
367 assert_eq!(expected.len(), FEATURE_ID_TO_METRIC_VALUE.len());
368
369 for (feature_id, metric_value) in &*FEATURE_ID_TO_METRIC_VALUE {
370 let expected = expected.get(format!("{feature_id}").as_str());
371 assert_eq!(
372 expected.unwrap_or_else(|| panic!("Expected {feature_id} to have value `{metric_value}` but it was `{expected:?}` instead.")),
373 metric_value,
374 );
375 }
376 }
377
378 #[test]
379 fn test_base64_iter() {
380 let ids: Vec<String> = Base64Iterator::new().take(MAX_METRICS_ID_NUMBER).collect();
382 assert_eq!("A", ids[0]);
383 assert_eq!("Z", ids[25]);
384 assert_eq!("a", ids[26]);
385 assert_eq!("z", ids[51]);
386 assert_eq!("0", ids[52]);
387 assert_eq!("9", ids[61]);
388 assert_eq!("+", ids[62]);
389 assert_eq!("-", ids[63]);
390 assert_eq!("AA", ids[64]);
391 assert_eq!("AB", ids[65]);
392 assert_eq!("A-", ids[127]);
393 assert_eq!("BA", ids[128]);
394 assert_eq!("Ed", ids[349]);
395 }
396
397 #[test]
398 fn test_drop_unfinished_metrics_to_fit() {
399 let csv = "A,10BC,E";
400 assert_eq!("A", drop_unfinished_metrics_to_fit(csv, 5));
401
402 let csv = "A10B,CE";
403 assert_eq!("A10B", drop_unfinished_metrics_to_fit(csv, 5));
404
405 let csv = "A10BC,E";
406 assert_eq!("A10BC", drop_unfinished_metrics_to_fit(csv, 5));
407
408 let csv = "A10BCE";
409 assert_eq!("A10BC", drop_unfinished_metrics_to_fit(csv, 5));
410
411 let csv = "A";
412 assert_eq!("A", drop_unfinished_metrics_to_fit(csv, 5));
413
414 let csv = "A,B";
415 assert_eq!("A,B", drop_unfinished_metrics_to_fit(csv, 5));
416 }
417}