aws_smithy_runtime_api/client/http/
telemetry.rs1use crate::client::connection::ConnectionMetadata;
13use aws_smithy_types::config_bag::{Storable, StoreReplace};
14use std::fmt;
15use std::sync::{Arc, Mutex, MutexGuard};
16use std::time::{Duration, SystemTime};
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20#[non_exhaustive]
21pub enum ConnectionUsage {
22 Fresh,
27 Reused,
29}
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33#[non_exhaustive]
34pub struct ConnectionAcquisitionTelemetry {
35 duration: Option<Duration>,
36 usage: ConnectionUsage,
37}
38
39impl ConnectionAcquisitionTelemetry {
40 pub fn new(duration: Duration, usage: ConnectionUsage) -> Self {
42 Self {
43 duration: Some(duration),
44 usage,
45 }
46 }
47
48 pub fn from_interval(
53 started_at: SystemTime,
54 completed_at: SystemTime,
55 usage: ConnectionUsage,
56 ) -> Self {
57 Self {
58 duration: completed_at.duration_since(started_at).ok(),
59 usage,
60 }
61 }
62
63 pub fn duration(&self) -> Option<Duration> {
68 self.duration
69 }
70
71 pub fn usage(&self) -> ConnectionUsage {
73 self.usage
74 }
75}
76
77#[derive(Clone, Debug, Default)]
79#[non_exhaustive]
80pub struct HttpAttemptTelemetry {
81 selection: Option<ConnectionSelection>,
82 connector_call_duration: Option<Duration>,
83}
84
85impl HttpAttemptTelemetry {
86 pub fn acquisition(&self) -> Option<&ConnectionAcquisitionTelemetry> {
88 self.selection
89 .as_ref()
90 .map(|selection| &selection.acquisition)
91 }
92
93 pub fn connection(&self) -> Option<&ConnectionMetadata> {
95 self.selection
96 .as_ref()
97 .map(|selection| &selection.connection)
98 }
99
100 pub fn connector_call_duration(&self) -> Option<Duration> {
105 self.connector_call_duration
106 }
107}
108
109#[derive(Clone, Debug)]
110struct ConnectionSelection {
111 acquisition: ConnectionAcquisitionTelemetry,
112 connection: ConnectionMetadata,
113}
114
115#[derive(Clone, Default)]
120pub struct CaptureHttpAttemptTelemetry {
121 state: Arc<Mutex<HttpAttemptTelemetry>>,
122}
123
124impl CaptureHttpAttemptTelemetry {
125 pub fn new() -> Self {
127 Self::default()
128 }
129
130 pub fn get(&self) -> HttpAttemptTelemetry {
132 self.lock().clone()
133 }
134
135 pub fn record_connector_call_duration(&self, duration: Duration) -> bool {
139 let mut state = self.lock();
140 if state.connector_call_duration.is_some() {
141 return false;
142 }
143 state.connector_call_duration = Some(duration);
144 true
145 }
146
147 pub fn record_connection_selection(
152 &self,
153 acquisition: ConnectionAcquisitionTelemetry,
154 connection: ConnectionMetadata,
155 ) -> bool {
156 let mut state = self.lock();
157 if state.selection.is_some() {
158 return false;
159 }
160 state.selection = Some(ConnectionSelection {
161 acquisition,
162 connection,
163 });
164 true
165 }
166
167 fn lock(&self) -> MutexGuard<'_, HttpAttemptTelemetry> {
168 self.state
169 .lock()
170 .unwrap_or_else(std::sync::PoisonError::into_inner)
171 }
172}
173
174impl fmt::Debug for CaptureHttpAttemptTelemetry {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 f.debug_struct("CaptureHttpAttemptTelemetry")
177 .finish_non_exhaustive()
178 }
179}
180
181impl Storable for CaptureHttpAttemptTelemetry {
182 type Storer = StoreReplace<Self>;
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 fn connection() -> ConnectionMetadata {
190 ConnectionMetadata::builder()
191 .proxied(false)
192 .poison_fn(|| {})
193 .build()
194 }
195
196 #[test]
197 fn records_connector_call_and_selection_independently() {
198 let capture = CaptureHttpAttemptTelemetry::new();
199 let acquisition =
200 ConnectionAcquisitionTelemetry::new(Duration::from_millis(3), ConnectionUsage::Fresh);
201
202 assert!(capture.record_connection_selection(acquisition, connection()));
203 assert!(capture.record_connector_call_duration(Duration::from_millis(7)));
204
205 let telemetry = capture.get();
206 assert_eq!(telemetry.acquisition(), Some(&acquisition));
207 assert_eq!(
208 telemetry.connector_call_duration(),
209 Some(Duration::from_millis(7))
210 );
211 assert!(telemetry.connection().is_some());
212 }
213
214 #[test]
215 fn first_recorded_value_wins() {
216 let capture = CaptureHttpAttemptTelemetry::new();
217 let first =
218 ConnectionAcquisitionTelemetry::new(Duration::from_millis(3), ConnectionUsage::Fresh);
219 let second =
220 ConnectionAcquisitionTelemetry::new(Duration::from_millis(9), ConnectionUsage::Reused);
221 assert!(capture.record_connection_selection(first, connection()));
222 assert!(!capture.record_connection_selection(second, connection()));
223 assert!(capture.record_connector_call_duration(Duration::from_millis(4)));
224 assert!(!capture.record_connector_call_duration(Duration::from_millis(8)));
225
226 let telemetry = capture.get();
227 assert_eq!(telemetry.acquisition(), Some(&first));
228 assert_eq!(
229 telemetry.connector_call_duration(),
230 Some(Duration::from_millis(4))
231 );
232 }
233
234 #[test]
235 fn backwards_acquisition_interval_retains_selection_facts() {
236 let started_at = SystemTime::UNIX_EPOCH + Duration::from_secs(2);
237 let completed_at = SystemTime::UNIX_EPOCH + Duration::from_secs(1);
238
239 let acquisition = ConnectionAcquisitionTelemetry::from_interval(
240 started_at,
241 completed_at,
242 ConnectionUsage::Fresh,
243 );
244 let capture = CaptureHttpAttemptTelemetry::new();
245 assert!(capture.record_connection_selection(acquisition, connection()));
246
247 let telemetry = capture.get();
248 assert_eq!(telemetry.acquisition().expect("selection").duration(), None);
249 assert_eq!(
250 telemetry.acquisition().expect("selection").usage(),
251 ConnectionUsage::Fresh
252 );
253 assert!(telemetry.connection().is_some());
254 }
255}