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