1use fidl_fuchsia_wlan_stats as fidl_stats;
6use fuchsia_async::TimeoutExt;
7use futures::lock::Mutex;
8
9use log::{error, warn};
10use std::collections::HashMap;
11use std::sync::Arc;
12use windowed_stats::experimental::clock::Timed;
13use windowed_stats::experimental::series::interpolation::{Constant, LastSample};
14use windowed_stats::experimental::series::statistic::{
15 ArithmeticMean, Last, LatchMax, Max, Min, Sum,
16};
17use windowed_stats::experimental::series::{SamplingProfile, TimeMatrix};
18use windowed_stats::experimental::serve::{InspectSender, InspectedTimeMatrix};
19
20const GET_IFACE_STATS_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(5);
22
23#[derive(Debug)]
24enum IfaceState {
25 NotAvailable,
26 Created { iface_id: u16, telemetry_proxy: Option<fidl_fuchsia_wlan_sme::TelemetryProxy> },
27}
28
29type CountersTimeSeriesMap = HashMap<u16, InspectedTimeMatrix<u64>>;
30type GaugesTimeSeriesMap = HashMap<u16, Vec<InspectedTimeMatrix<i64>>>;
31
32pub struct ClientIfaceCountersLogger<S> {
33 iface_state: Arc<Mutex<IfaceState>>,
34 monitor_svc_proxy: fidl_fuchsia_wlan_device_service::DeviceMonitorProxy,
35 time_series_stats: IfaceCountersTimeSeries,
36 driver_counters_time_matrix_client: S,
37 driver_counters_time_series: Arc<Mutex<CountersTimeSeriesMap>>,
38 driver_gauges_time_matrix_client: S,
39 driver_gauges_time_series: Arc<Mutex<GaugesTimeSeriesMap>>,
40}
41
42impl<S: InspectSender> ClientIfaceCountersLogger<S> {
43 pub fn new(
44 monitor_svc_proxy: fidl_fuchsia_wlan_device_service::DeviceMonitorProxy,
45 time_matrix_client: &S,
46 driver_counters_time_matrix_client: S,
47 driver_gauges_time_matrix_client: S,
48 ) -> Self {
49 Self {
50 iface_state: Arc::new(Mutex::new(IfaceState::NotAvailable)),
51 monitor_svc_proxy,
52 time_series_stats: IfaceCountersTimeSeries::new(time_matrix_client),
53 driver_counters_time_matrix_client,
54 driver_counters_time_series: Arc::new(Mutex::new(HashMap::new())),
55 driver_gauges_time_matrix_client,
56 driver_gauges_time_series: Arc::new(Mutex::new(HashMap::new())),
57 }
58 }
59
60 pub async fn handle_iface_created(&self, iface_id: u16) {
61 let (proxy, server) = fidl::endpoints::create_proxy();
62 let telemetry_proxy = match self.monitor_svc_proxy.get_sme_telemetry(iface_id, server).await
63 {
64 Ok(Ok(())) => {
65 let (inspect_counter_configs, inspect_gauge_configs) = match proxy
66 .query_telemetry_support()
67 .await
68 {
69 Ok(Ok(support)) => {
70 (support.inspect_counter_configs, support.inspect_gauge_configs)
71 }
72 Ok(Err(code)) => {
73 warn!("Failed to query telemetry support with status code {}. No driver-specific stats will be captured", code);
74 (None, None)
75 }
76 Err(e) => {
77 error!("Failed to query telemetry support with error {}. No driver-specific stats will be captured", e);
78 (None, None)
79 }
80 };
81 if let Some(inspect_counter_configs) = &inspect_counter_configs {
82 let mut driver_counters_time_series =
83 self.driver_counters_time_series.lock().await;
84 for inspect_counter_config in inspect_counter_configs {
85 if let fidl_stats::InspectCounterConfig {
86 counter_id: Some(counter_id),
87 counter_name: Some(counter_name),
88 ..
89 } = inspect_counter_config
90 {
91 let _time_matrix_ref = driver_counters_time_series
92 .entry(*counter_id)
93 .or_insert_with(|| {
94 self.driver_counters_time_matrix_client.inspect_time_matrix(
95 counter_name,
96 TimeMatrix::<LatchMax<u64>, LastSample>::new(
97 SamplingProfile::balanced(),
98 LastSample::or(0),
99 ),
100 )
101 });
102 }
103 }
104 }
105 if let Some(inspect_gauge_configs) = &inspect_gauge_configs {
106 let mut driver_gauges_time_series = self.driver_gauges_time_series.lock().await;
107 for inspect_gauge_config in inspect_gauge_configs {
108 if let fidl_stats::InspectGaugeConfig {
109 gauge_id: Some(gauge_id),
110 gauge_name: Some(gauge_name),
111 statistics: Some(statistics),
112 ..
113 } = inspect_gauge_config
114 {
115 for statistic in statistics {
116 if let Some(time_matrix) = create_time_series_for_gauge(
117 &self.driver_gauges_time_matrix_client,
118 gauge_name,
119 statistic,
120 ) {
121 let time_matrices =
122 driver_gauges_time_series.entry(*gauge_id).or_default();
123 time_matrices.push(time_matrix);
124 }
125 }
126 }
127 }
128 }
129 Some(proxy)
130 }
131 Ok(Err(e)) => {
132 error!("Request for SME telemetry for iface {} completed with error {}. No telemetry will be captured.", iface_id, e);
133 None
134 }
135 Err(e) => {
136 error!("Failed to request SME telemetry for iface {} with error {}. No telemetry will be captured.", iface_id, e);
137 None
138 }
139 };
140 *self.iface_state.lock().await = IfaceState::Created { iface_id, telemetry_proxy }
141 }
142
143 pub async fn handle_iface_destroyed(&self, iface_id: u16) {
144 let destroyed = matches!(*self.iface_state.lock().await, IfaceState::Created { iface_id: existing_iface_id, .. } if iface_id == existing_iface_id);
145 if destroyed {
146 *self.iface_state.lock().await = IfaceState::NotAvailable;
147 }
148 }
149
150 pub async fn handle_periodic_telemetry(&self, is_connected: bool) {
151 match &*self.iface_state.lock().await {
152 IfaceState::NotAvailable => (),
153 IfaceState::Created { telemetry_proxy, .. } => {
154 if let Some(telemetry_proxy) = &telemetry_proxy {
155 match telemetry_proxy
156 .get_iface_stats()
157 .on_timeout(GET_IFACE_STATS_TIMEOUT, || {
158 warn!("Timed out waiting for iface stats");
159 Ok(Err(zx::Status::TIMED_OUT.into_raw()))
160 })
161 .await
162 {
163 Ok(Ok(stats)) => {
164 if let Some(counters) = &stats.driver_specific_counters {
166 let time_series = Arc::clone(&self.driver_counters_time_series);
167 log_driver_specific_counters(&counters[..], time_series).await;
168 }
169 if let Some(gauges) = &stats.driver_specific_gauges {
171 let time_series = Arc::clone(&self.driver_gauges_time_series);
172 log_driver_specific_gauges(&gauges[..], time_series).await;
173 }
174 log_connection_stats(
175 &stats,
176 &self.time_series_stats,
177 Arc::clone(&self.driver_counters_time_series),
178 Arc::clone(&self.driver_gauges_time_series),
179 )
180 .await;
181 }
182 error => {
183 if is_connected {
186 warn!("Failed to get interface stats: {:?}", error);
187 }
188 }
189 }
190 }
191 }
192 }
193 }
194}
195
196fn create_time_series_for_gauge<S: InspectSender>(
197 time_matrix_client: &S,
198 gauge_name: &str,
199 statistic: &fidl_stats::GaugeStatistic,
200) -> Option<InspectedTimeMatrix<i64>> {
201 match statistic {
202 fidl_stats::GaugeStatistic::Min => Some(time_matrix_client.inspect_time_matrix(
203 format!("{}.min", gauge_name),
204 TimeMatrix::<Min<i64>, Constant>::new(SamplingProfile::balanced(), Constant::default()),
205 )),
206 fidl_stats::GaugeStatistic::Max => Some(time_matrix_client.inspect_time_matrix(
207 format!("{}.max", gauge_name),
208 TimeMatrix::<Max<i64>, Constant>::new(SamplingProfile::balanced(), Constant::default()),
209 )),
210 fidl_stats::GaugeStatistic::Sum => Some(time_matrix_client.inspect_time_matrix(
211 format!("{}.sum", gauge_name),
212 TimeMatrix::<Sum<i64>, Constant>::new(SamplingProfile::balanced(), Constant::default()),
213 )),
214 fidl_stats::GaugeStatistic::Last => Some(time_matrix_client.inspect_time_matrix(
215 format!("{}.last", gauge_name),
216 TimeMatrix::<Last<i64>, Constant>::new(
217 SamplingProfile::balanced(),
218 Constant::default(),
219 ),
220 )),
221 fidl_stats::GaugeStatistic::Mean => Some(time_matrix_client.inspect_time_matrix(
222 format!("{}.mean", gauge_name),
223 TimeMatrix::<ArithmeticMean<i64>, Constant>::new(
224 SamplingProfile::balanced(),
225 Constant::default(),
226 ),
227 )),
228 _ => None,
229 }
230}
231
232async fn log_connection_stats(
233 stats: &fidl_stats::IfaceStats,
234 time_series_stats: &IfaceCountersTimeSeries,
235 driver_counters_time_series: Arc<Mutex<CountersTimeSeriesMap>>,
236 driver_gauges_time_series: Arc<Mutex<GaugesTimeSeriesMap>>,
237) {
238 let connection_stats = match &stats.connection_stats {
239 Some(counters) => counters,
240 None => return,
241 };
242
243 match &connection_stats.connection_id {
246 Some(_connection_id) => (),
247 _ => {
248 warn!("connection_id is not present, no connection counters will be logged");
249 return;
250 }
251 }
252
253 if let fidl_stats::ConnectionStats {
254 rx_unicast_total: Some(rx_unicast_total),
255 rx_unicast_drop: Some(rx_unicast_drop),
256 ..
257 } = connection_stats
258 {
259 time_series_stats.log_rx_unicast_total(*rx_unicast_total);
260 time_series_stats.log_rx_unicast_drop(*rx_unicast_drop);
261 }
262
263 if let fidl_stats::ConnectionStats {
264 tx_total: Some(tx_total), tx_drop: Some(tx_drop), ..
265 } = connection_stats
266 {
267 time_series_stats.log_tx_total(*tx_total);
268 time_series_stats.log_tx_drop(*tx_drop);
269 }
270
271 if let Some(counters) = &connection_stats.driver_specific_counters {
273 log_driver_specific_counters(&counters[..], driver_counters_time_series).await;
274 }
275 if let Some(gauges) = &connection_stats.driver_specific_gauges {
277 log_driver_specific_gauges(&gauges[..], driver_gauges_time_series).await;
278 }
279}
280
281async fn log_driver_specific_counters(
282 driver_specific_counters: &[fidl_stats::UnnamedCounter],
283 driver_counters_time_series: Arc<Mutex<CountersTimeSeriesMap>>,
284) {
285 let time_series_map = driver_counters_time_series.lock().await;
286 for counter in driver_specific_counters {
287 if let Some(ts) = time_series_map.get(&counter.id) {
288 ts.fold_or_log_error(Timed::now(counter.count));
289 }
290 }
291}
292
293async fn log_driver_specific_gauges(
294 driver_specific_gauges: &[fidl_stats::UnnamedGauge],
295 driver_gauges_time_series: Arc<Mutex<GaugesTimeSeriesMap>>,
296) {
297 let time_series_map = driver_gauges_time_series.lock().await;
298 for gauge in driver_specific_gauges {
299 if let Some(time_matrices) = time_series_map.get(&gauge.id) {
300 for ts in time_matrices {
301 ts.fold_or_log_error(Timed::now(gauge.value));
302 }
303 }
304 }
305}
306
307#[derive(Debug, Clone)]
308struct IfaceCountersTimeSeries {
309 rx_unicast_total: InspectedTimeMatrix<u64>,
310 rx_unicast_drop: InspectedTimeMatrix<u64>,
311 tx_total: InspectedTimeMatrix<u64>,
312 tx_drop: InspectedTimeMatrix<u64>,
313}
314
315impl IfaceCountersTimeSeries {
316 pub fn new<S: InspectSender>(client: &S) -> Self {
317 let rx_unicast_total = client.inspect_time_matrix(
318 "rx_unicast_total",
319 TimeMatrix::<LatchMax<u64>, LastSample>::new(
320 SamplingProfile::balanced(),
321 LastSample::or(0),
322 ),
323 );
324 let rx_unicast_drop = client.inspect_time_matrix(
325 "rx_unicast_drop",
326 TimeMatrix::<LatchMax<u64>, LastSample>::new(
327 SamplingProfile::balanced(),
328 LastSample::or(0),
329 ),
330 );
331 let tx_total = client.inspect_time_matrix(
332 "tx_total",
333 TimeMatrix::<LatchMax<u64>, LastSample>::new(
334 SamplingProfile::balanced(),
335 LastSample::or(0),
336 ),
337 );
338 let tx_drop = client.inspect_time_matrix(
339 "tx_drop",
340 TimeMatrix::<LatchMax<u64>, LastSample>::new(
341 SamplingProfile::balanced(),
342 LastSample::or(0),
343 ),
344 );
345 Self { rx_unicast_total, rx_unicast_drop, tx_total, tx_drop }
346 }
347
348 fn log_rx_unicast_total(&self, data: u64) {
349 self.rx_unicast_total.fold_or_log_error(Timed::now(data));
350 }
351
352 fn log_rx_unicast_drop(&self, data: u64) {
353 self.rx_unicast_drop.fold_or_log_error(Timed::now(data));
354 }
355
356 fn log_tx_total(&self, data: u64) {
357 self.tx_total.fold_or_log_error(Timed::now(data));
358 }
359
360 fn log_tx_drop(&self, data: u64) {
361 self.tx_drop.fold_or_log_error(Timed::now(data));
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use crate::testing::*;
369 use futures::TryStreamExt;
370 use std::pin::pin;
371 use std::task::Poll;
372 use windowed_stats::experimental::testing::{MockTimeMatrixClient, TimeMatrixCall};
373 use wlan_common::assert_variant;
374
375 const IFACE_ID: u16 = 66;
376
377 #[fuchsia::test]
378 fn test_handle_iface_created() {
379 let mut test_helper = setup_test();
380 let driver_counters_mock_matrix_client = MockTimeMatrixClient::new();
381 let driver_gauges_mock_matrix_client = MockTimeMatrixClient::new();
382 let logger = ClientIfaceCountersLogger::new(
383 test_helper.monitor_svc_proxy.clone(),
384 &test_helper.mock_time_matrix_client,
385 driver_counters_mock_matrix_client.clone(),
386 driver_gauges_mock_matrix_client.clone(),
387 );
388
389 let mut handle_iface_created_fut = pin!(logger.handle_iface_created(IFACE_ID));
390 assert_eq!(
391 test_helper.run_and_handle_get_sme_telemetry(&mut handle_iface_created_fut),
392 Poll::Pending
393 );
394
395 let mocked_inspect_counter_configs = vec![fidl_stats::InspectCounterConfig {
396 counter_id: Some(1),
397 counter_name: Some("foo_counter".to_string()),
398 ..Default::default()
399 }];
400 let telemetry_support = fidl_stats::TelemetrySupport {
401 inspect_counter_configs: Some(mocked_inspect_counter_configs),
402 ..Default::default()
403 };
404 assert_eq!(
405 test_helper.run_and_respond_query_telemetry_support(
406 &mut handle_iface_created_fut,
407 Ok(&telemetry_support)
408 ),
409 Poll::Ready(())
410 );
411
412 assert_variant!(logger.iface_state.try_lock().as_deref(), Some(IfaceState::Created { .. }));
413 let driver_counters_time_series = logger.driver_counters_time_series.try_lock().unwrap();
414 assert_eq!(driver_counters_time_series.keys().copied().collect::<Vec<u16>>(), vec![1u16],);
415 }
416
417 #[fuchsia::test]
418 fn test_handle_periodic_telemetry_connection_stats() {
419 let mut test_helper = setup_test();
420 let driver_counters_mock_matrix_client = MockTimeMatrixClient::new();
421 let driver_gauges_mock_matrix_client = MockTimeMatrixClient::new();
422 let logger = ClientIfaceCountersLogger::new(
423 test_helper.monitor_svc_proxy.clone(),
424 &test_helper.mock_time_matrix_client,
425 driver_counters_mock_matrix_client.clone(),
426 driver_gauges_mock_matrix_client.clone(),
427 );
428
429 handle_iface_created(&mut test_helper, &logger);
431
432 let is_connected = true;
433 let mut test_fut = pin!(logger.handle_periodic_telemetry(is_connected));
434 let iface_stats = fidl_stats::IfaceStats {
435 connection_stats: Some(fidl_stats::ConnectionStats {
436 connection_id: Some(1),
437 rx_unicast_total: Some(100),
438 rx_unicast_drop: Some(5),
439 rx_multicast: Some(30),
440 tx_total: Some(50),
441 tx_drop: Some(2),
442 ..Default::default()
443 }),
444 ..Default::default()
445 };
446 assert_eq!(
447 test_helper.run_and_respond_iface_stats_req(&mut test_fut, Ok(&iface_stats)),
448 Poll::Ready(())
449 );
450
451 let mut time_matrix_calls = test_helper.mock_time_matrix_client.drain_calls();
452 assert_eq!(
453 &time_matrix_calls.drain::<u64>("rx_unicast_total")[..],
454 &[TimeMatrixCall::Fold(Timed::now(100u64))]
455 );
456 assert_eq!(
457 &time_matrix_calls.drain::<u64>("rx_unicast_drop")[..],
458 &[TimeMatrixCall::Fold(Timed::now(5u64))]
459 );
460 assert_eq!(
461 &time_matrix_calls.drain::<u64>("tx_total")[..],
462 &[TimeMatrixCall::Fold(Timed::now(50u64))]
463 );
464 assert_eq!(
465 &time_matrix_calls.drain::<u64>("tx_drop")[..],
466 &[TimeMatrixCall::Fold(Timed::now(2u64))]
467 );
468 }
469
470 #[fuchsia::test]
471 fn test_handle_periodic_telemetry_driver_specific_counters() {
472 let mut test_helper = setup_test();
473 let driver_counters_mock_matrix_client = MockTimeMatrixClient::new();
474 let driver_gauges_mock_matrix_client = MockTimeMatrixClient::new();
475 let logger = ClientIfaceCountersLogger::new(
476 test_helper.monitor_svc_proxy.clone(),
477 &test_helper.mock_time_matrix_client,
478 driver_counters_mock_matrix_client.clone(),
479 driver_gauges_mock_matrix_client.clone(),
480 );
481
482 let mut handle_iface_created_fut = pin!(logger.handle_iface_created(IFACE_ID));
483 assert_eq!(
484 test_helper.run_and_handle_get_sme_telemetry(&mut handle_iface_created_fut),
485 Poll::Pending
486 );
487
488 let mocked_inspect_configs = vec![
489 fidl_stats::InspectCounterConfig {
490 counter_id: Some(1),
491 counter_name: Some("foo_counter".to_string()),
492 ..Default::default()
493 },
494 fidl_stats::InspectCounterConfig {
495 counter_id: Some(2),
496 counter_name: Some("bar_counter".to_string()),
497 ..Default::default()
498 },
499 fidl_stats::InspectCounterConfig {
500 counter_id: Some(3),
501 counter_name: Some("baz_counter".to_string()),
502 ..Default::default()
503 },
504 ];
505 let telemetry_support = fidl_stats::TelemetrySupport {
506 inspect_counter_configs: Some(mocked_inspect_configs),
507 ..Default::default()
508 };
509 assert_eq!(
510 test_helper.run_and_respond_query_telemetry_support(
511 &mut handle_iface_created_fut,
512 Ok(&telemetry_support)
513 ),
514 Poll::Ready(())
515 );
516
517 let is_connected = true;
518 let mut test_fut = pin!(logger.handle_periodic_telemetry(is_connected));
519 let iface_stats = fidl_stats::IfaceStats {
520 driver_specific_counters: Some(vec![fidl_stats::UnnamedCounter { id: 1, count: 50 }]),
521 connection_stats: Some(fidl_stats::ConnectionStats {
522 connection_id: Some(1),
523 driver_specific_counters: Some(vec![
524 fidl_stats::UnnamedCounter { id: 2, count: 100 },
525 fidl_stats::UnnamedCounter { id: 3, count: 150 },
526 fidl_stats::UnnamedCounter { id: 4, count: 200 },
528 ]),
529 ..Default::default()
530 }),
531 ..Default::default()
532 };
533 assert_eq!(
534 test_helper.run_and_respond_iface_stats_req(&mut test_fut, Ok(&iface_stats)),
535 Poll::Ready(())
536 );
537
538 let time_matrix_calls = test_helper.mock_time_matrix_client.drain_calls();
539 assert!(time_matrix_calls.is_empty());
540
541 let mut driver_counters_matrix_calls = driver_counters_mock_matrix_client.drain_calls();
542 assert_eq!(
543 &driver_counters_matrix_calls.drain::<u64>("foo_counter")[..],
544 &[TimeMatrixCall::Fold(Timed::now(50))]
545 );
546 assert_eq!(
547 &driver_counters_matrix_calls.drain::<u64>("bar_counter")[..],
548 &[TimeMatrixCall::Fold(Timed::now(100))]
549 );
550 assert_eq!(
551 &driver_counters_matrix_calls.drain::<u64>("baz_counter")[..],
552 &[TimeMatrixCall::Fold(Timed::now(150))]
553 );
554
555 let driver_gauges_matrix_calls = driver_gauges_mock_matrix_client.drain_calls();
556 assert!(driver_gauges_matrix_calls.is_empty());
557 }
558
559 #[fuchsia::test]
560 fn test_handle_periodic_telemetry_driver_specific_gauges() {
561 let mut test_helper = setup_test();
562 let driver_counters_mock_matrix_client = MockTimeMatrixClient::new();
563 let driver_gauges_mock_matrix_client = MockTimeMatrixClient::new();
564 let logger = ClientIfaceCountersLogger::new(
565 test_helper.monitor_svc_proxy.clone(),
566 &test_helper.mock_time_matrix_client,
567 driver_counters_mock_matrix_client.clone(),
568 driver_gauges_mock_matrix_client.clone(),
569 );
570
571 let mut handle_iface_created_fut = pin!(logger.handle_iface_created(IFACE_ID));
572 assert_eq!(
573 test_helper.run_and_handle_get_sme_telemetry(&mut handle_iface_created_fut),
574 Poll::Pending
575 );
576
577 let mocked_inspect_configs = vec![
578 fidl_stats::InspectGaugeConfig {
579 gauge_id: Some(1),
580 gauge_name: Some("foo_gauge".to_string()),
581 statistics: Some(vec![
582 fidl_stats::GaugeStatistic::Mean,
583 fidl_stats::GaugeStatistic::Last,
584 ]),
585 ..Default::default()
586 },
587 fidl_stats::InspectGaugeConfig {
588 gauge_id: Some(2),
589 gauge_name: Some("bar_gauge".to_string()),
590 statistics: Some(vec![
591 fidl_stats::GaugeStatistic::Min,
592 fidl_stats::GaugeStatistic::Sum,
593 ]),
594 ..Default::default()
595 },
596 fidl_stats::InspectGaugeConfig {
597 gauge_id: Some(3),
598 gauge_name: Some("baz_gauge".to_string()),
599 statistics: Some(vec![fidl_stats::GaugeStatistic::Max]),
600 ..Default::default()
601 },
602 ];
603 let telemetry_support = fidl_stats::TelemetrySupport {
604 inspect_gauge_configs: Some(mocked_inspect_configs),
605 ..Default::default()
606 };
607 assert_eq!(
608 test_helper.run_and_respond_query_telemetry_support(
609 &mut handle_iface_created_fut,
610 Ok(&telemetry_support)
611 ),
612 Poll::Ready(())
613 );
614
615 let is_connected = true;
616 let mut test_fut = pin!(logger.handle_periodic_telemetry(is_connected));
617 let iface_stats = fidl_stats::IfaceStats {
618 driver_specific_gauges: Some(vec![fidl_stats::UnnamedGauge { id: 1, value: 50 }]),
619 connection_stats: Some(fidl_stats::ConnectionStats {
620 connection_id: Some(1),
621 driver_specific_gauges: Some(vec![
622 fidl_stats::UnnamedGauge { id: 2, value: 100 },
623 fidl_stats::UnnamedGauge { id: 3, value: 150 },
624 fidl_stats::UnnamedGauge { id: 4, value: 200 },
626 ]),
627 ..Default::default()
628 }),
629 ..Default::default()
630 };
631 assert_eq!(
632 test_helper.run_and_respond_iface_stats_req(&mut test_fut, Ok(&iface_stats)),
633 Poll::Ready(())
634 );
635
636 let time_matrix_calls = test_helper.mock_time_matrix_client.drain_calls();
637 assert!(time_matrix_calls.is_empty());
638
639 let driver_counters_matrix_calls = driver_counters_mock_matrix_client.drain_calls();
640 assert!(driver_counters_matrix_calls.is_empty());
641
642 let mut driver_gauges_matrix_calls = driver_gauges_mock_matrix_client.drain_calls();
643 assert_eq!(
644 &driver_gauges_matrix_calls.drain::<i64>("foo_gauge.mean")[..],
645 &[TimeMatrixCall::Fold(Timed::now(50))]
646 );
647 assert_eq!(
648 &driver_gauges_matrix_calls.drain::<i64>("foo_gauge.last")[..],
649 &[TimeMatrixCall::Fold(Timed::now(50))]
650 );
651 assert_eq!(
652 &driver_gauges_matrix_calls.drain::<i64>("bar_gauge.min")[..],
653 &[TimeMatrixCall::Fold(Timed::now(100))]
654 );
655 assert_eq!(
656 &driver_gauges_matrix_calls.drain::<i64>("bar_gauge.sum")[..],
657 &[TimeMatrixCall::Fold(Timed::now(100))]
658 );
659 assert_eq!(
660 &driver_gauges_matrix_calls.drain::<i64>("baz_gauge.max")[..],
661 &[TimeMatrixCall::Fold(Timed::now(150))]
662 );
663 }
664
665 #[fuchsia::test]
666 fn test_handle_iface_destroyed() {
667 let mut test_helper = setup_test();
668 let driver_counters_mock_matrix_client = MockTimeMatrixClient::new();
669 let driver_gauges_mock_matrix_client = MockTimeMatrixClient::new();
670 let logger = ClientIfaceCountersLogger::new(
671 test_helper.monitor_svc_proxy.clone(),
672 &test_helper.mock_time_matrix_client,
673 driver_counters_mock_matrix_client.clone(),
674 driver_gauges_mock_matrix_client.clone(),
675 );
676
677 handle_iface_created(&mut test_helper, &logger);
679
680 let mut handle_iface_destroyed_fut = pin!(logger.handle_iface_destroyed(IFACE_ID));
681 assert_eq!(
682 test_helper.exec.run_until_stalled(&mut handle_iface_destroyed_fut),
683 Poll::Ready(())
684 );
685
686 let is_connected = true;
687 let mut test_fut = pin!(logger.handle_periodic_telemetry(is_connected));
688 assert_eq!(test_helper.exec.run_until_stalled(&mut test_fut), Poll::Ready(()));
689 let telemetry_svc_stream = test_helper.telemetry_svc_stream.as_mut().unwrap();
690 let mut telemetry_svc_req_fut = pin!(telemetry_svc_stream.try_next());
691 match test_helper.exec.run_until_stalled(&mut telemetry_svc_req_fut) {
693 Poll::Ready(Ok(None)) => (),
694 other => panic!("unexpected variant: {:?}", other),
695 }
696 }
697
698 fn handle_iface_created<S: InspectSender>(
699 test_helper: &mut TestHelper,
700 logger: &ClientIfaceCountersLogger<S>,
701 ) {
702 let mut handle_iface_created_fut = pin!(logger.handle_iface_created(IFACE_ID));
703 assert_eq!(
704 test_helper.run_and_handle_get_sme_telemetry(&mut handle_iface_created_fut),
705 Poll::Pending
706 );
707 let telemetry_support = fidl_stats::TelemetrySupport::default();
708 assert_eq!(
709 test_helper.run_and_respond_query_telemetry_support(
710 &mut handle_iface_created_fut,
711 Ok(&telemetry_support)
712 ),
713 Poll::Ready(())
714 );
715 }
716}