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 ObservabilityOtelMetrics => Some(BusinessMetric::ObservabilityOtelMetrics),
214 otherwise => {
215 tracing::warn!(
219 "Attempted to provide `BusinessMetric` for `{otherwise:?}`, which is not recognized in the current version of the `aws-runtime` crate. \
220 Consider upgrading to the latest version to ensure that all tracked features are properly reported in your metrics."
221 );
222 None
223 }
224 }
225 }
226}
227
228impl ProvideBusinessMetric for AwsSdkFeature {
229 fn provide_business_metric(&self) -> Option<BusinessMetric> {
230 use AwsSdkFeature::*;
231 match self {
232 AccountIdModePreferred => Some(BusinessMetric::AccountIdModePreferred),
233 AccountIdModeDisabled => Some(BusinessMetric::AccountIdModeDisabled),
234 AccountIdModeRequired => Some(BusinessMetric::AccountIdModeRequired),
235 S3Transfer => Some(BusinessMetric::S3Transfer),
236 SsoLoginDevice => Some(BusinessMetric::SsoLoginDevice),
237 SsoLoginAuth => Some(BusinessMetric::SsoLoginAuth),
238 }
239 }
240}
241
242impl ProvideBusinessMetric for AwsCredentialFeature {
243 fn provide_business_metric(&self) -> Option<BusinessMetric> {
244 use AwsCredentialFeature::*;
245 match self {
246 ResolvedAccountId => Some(BusinessMetric::ResolvedAccountId),
247 CredentialsCode => Some(BusinessMetric::CredentialsCode),
248 CredentialsEnvVars => Some(BusinessMetric::CredentialsEnvVars),
249 CredentialsEnvVarsStsWebIdToken => {
250 Some(BusinessMetric::CredentialsEnvVarsStsWebIdToken)
251 }
252 CredentialsStsAssumeRole => Some(BusinessMetric::CredentialsStsAssumeRole),
253 CredentialsStsAssumeRoleSaml => Some(BusinessMetric::CredentialsStsAssumeRoleSaml),
254 CredentialsStsAssumeRoleWebId => Some(BusinessMetric::CredentialsStsAssumeRoleWebId),
255 CredentialsStsFederationToken => Some(BusinessMetric::CredentialsStsFederationToken),
256 CredentialsStsSessionToken => Some(BusinessMetric::CredentialsStsSessionToken),
257 CredentialsProfile => Some(BusinessMetric::CredentialsProfile),
258 CredentialsProfileSourceProfile => {
259 Some(BusinessMetric::CredentialsProfileSourceProfile)
260 }
261 CredentialsProfileNamedProvider => {
262 Some(BusinessMetric::CredentialsProfileNamedProvider)
263 }
264 CredentialsProfileStsWebIdToken => {
265 Some(BusinessMetric::CredentialsProfileStsWebIdToken)
266 }
267 CredentialsProfileSso => Some(BusinessMetric::CredentialsProfileSso),
268 CredentialsSso => Some(BusinessMetric::CredentialsSso),
269 CredentialsProfileProcess => Some(BusinessMetric::CredentialsProfileProcess),
270 CredentialsProcess => Some(BusinessMetric::CredentialsProcess),
271 CredentialsHttp => Some(BusinessMetric::CredentialsHttp),
272 CredentialsImds => Some(BusinessMetric::CredentialsImds),
273 BearerServiceEnvVars => Some(BusinessMetric::BearerServiceEnvVars),
274 S3ExpressBucket => Some(BusinessMetric::S3ExpressBucket),
275 CredentialsProfileLogin => Some(BusinessMetric::CredentialsProfileLogin),
276 CredentialsLogin => Some(BusinessMetric::CredentialsLogin),
277 otherwise => {
278 tracing::warn!(
282 "Attempted to provide `BusinessMetric` for `{otherwise:?}`, which is not recognized in the current version of the `aws-runtime` crate. \
283 Consider upgrading to the latest version to ensure that all tracked features are properly reported in your metrics."
284 );
285 None
286 }
287 }
288 }
289}
290
291#[derive(Clone, Debug, Default)]
292pub(super) struct BusinessMetrics(Vec<BusinessMetric>);
293
294impl BusinessMetrics {
295 pub(super) fn push(&mut self, metric: BusinessMetric) {
296 self.0.push(metric);
297 }
298
299 pub(super) fn is_empty(&self) -> bool {
300 self.0.is_empty()
301 }
302}
303
304fn drop_unfinished_metrics_to_fit(csv: &str, max_len: usize) -> Cow<'_, str> {
305 if csv.len() <= max_len {
306 Cow::Borrowed(csv)
307 } else {
308 let truncated = &csv[..max_len];
309 if let Some(pos) = truncated.rfind(',') {
310 Cow::Owned(truncated[..pos].to_owned())
311 } else {
312 Cow::Owned(truncated.to_owned())
313 }
314 }
315}
316
317impl fmt::Display for BusinessMetrics {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 let metrics_values = self
321 .0
322 .iter()
323 .map(|feature_id| {
324 FEATURE_ID_TO_METRIC_VALUE
325 .get(feature_id)
326 .expect("{feature_id:?} should be found in `FEATURE_ID_TO_METRIC_VALUE`")
327 .clone()
328 })
329 .collect::<Vec<_>>()
330 .join(",");
331
332 let metrics_values = drop_unfinished_metrics_to_fit(
333 &metrics_values,
334 MAX_COMMA_SEPARATED_METRICS_VALUES_LENGTH,
335 );
336
337 write!(f, "m/{metrics_values}")
338 }
339}
340#[cfg(test)]
341mod tests {
342 use crate::user_agent::metrics::{
343 drop_unfinished_metrics_to_fit, Base64Iterator, FEATURE_ID_TO_METRIC_VALUE,
344 MAX_METRICS_ID_NUMBER,
345 };
346 use crate::user_agent::BusinessMetric;
347 use convert_case::{Boundary, Case, Casing};
348 use std::collections::HashMap;
349 use std::fmt::{Display, Formatter};
350
351 impl Display for BusinessMetric {
352 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
353 f.write_str(
354 &format!("{:?}", self)
355 .as_str()
356 .from_case(Case::Pascal)
357 .with_boundaries(&[Boundary::DigitUpper, Boundary::LowerUpper])
358 .to_case(Case::ScreamingSnake),
359 )
360 }
361 }
362
363 #[test]
364 fn feature_id_to_metric_value() {
365 const EXPECTED: &str = include_str!("test_data/feature_id_to_metric_value.json");
366
367 let expected: HashMap<&str, &str> = serde_json::from_str(EXPECTED).unwrap();
368 assert_eq!(expected.len(), FEATURE_ID_TO_METRIC_VALUE.len());
369
370 for (feature_id, metric_value) in &*FEATURE_ID_TO_METRIC_VALUE {
371 let expected = expected.get(format!("{feature_id}").as_str());
372 assert_eq!(
373 expected.unwrap_or_else(|| panic!("Expected {feature_id} to have value `{metric_value}` but it was `{expected:?}` instead.")),
374 metric_value,
375 );
376 }
377 }
378
379 #[test]
380 fn test_base64_iter() {
381 let ids: Vec<String> = Base64Iterator::new().take(MAX_METRICS_ID_NUMBER).collect();
383 assert_eq!("A", ids[0]);
384 assert_eq!("Z", ids[25]);
385 assert_eq!("a", ids[26]);
386 assert_eq!("z", ids[51]);
387 assert_eq!("0", ids[52]);
388 assert_eq!("9", ids[61]);
389 assert_eq!("+", ids[62]);
390 assert_eq!("-", ids[63]);
391 assert_eq!("AA", ids[64]);
392 assert_eq!("AB", ids[65]);
393 assert_eq!("A-", ids[127]);
394 assert_eq!("BA", ids[128]);
395 assert_eq!("Ed", ids[349]);
396 }
397
398 #[test]
399 fn test_drop_unfinished_metrics_to_fit() {
400 let csv = "A,10BC,E";
401 assert_eq!("A", drop_unfinished_metrics_to_fit(csv, 5));
402
403 let csv = "A10B,CE";
404 assert_eq!("A10B", drop_unfinished_metrics_to_fit(csv, 5));
405
406 let csv = "A10BC,E";
407 assert_eq!("A10BC", drop_unfinished_metrics_to_fit(csv, 5));
408
409 let csv = "A10BCE";
410 assert_eq!("A10BC", drop_unfinished_metrics_to_fit(csv, 5));
411
412 let csv = "A";
413 assert_eq!("A", drop_unfinished_metrics_to_fit(csv, 5));
414
415 let csv = "A,B";
416 assert_eq!("A,B", drop_unfinished_metrics_to_fit(csv, 5));
417 }
418}