1use crate::agent::{Context, Payload};
43use crate::event::{Event, Payload as EventPayload};
44use crate::message::base::{MessageEvent, MessengerType};
45use crate::service::{self as service, TryFromWithClient};
46use crate::service_context::ExternalServiceEvent;
47use crate::trace;
48
49use fuchsia_async as fasync;
50use fuchsia_inspect::{component, Node};
51use fuchsia_inspect_derive::{IValue, Inspect, WithInspect};
52use futures::StreamExt;
53use settings_inspect_utils::managed_inspect_map::ManagedInspectMap;
54use settings_inspect_utils::managed_inspect_queue::ManagedInspectQueue;
55use std::rc::Rc;
56
57const COMPLETED_CALLS_KEY: &str = "completed_calls";
59
60const PENDING_CALLS_KEY: &str = "pending_calls";
62
63const MAX_COMPLETED_CALLS: usize = 10;
66
67const MAX_PENDING_CALLS: usize = 10;
70
71#[derive(Debug, Default, Inspect)]
73struct ExternalApiCallInfo {
74 inspect_node: Node,
76
77 request: IValue<String>,
79
80 response: IValue<String>,
82
83 request_timestamp: IValue<String>,
85
86 response_timestamp: IValue<String>,
88}
89
90impl ExternalApiCallInfo {
91 fn new(
92 request: &str,
93 response: &str,
94 request_timestamp: &str,
95 response_timestamp: &str,
96 ) -> Self {
97 let mut info = Self::default();
98 info.request.iset(request.to_string());
99 info.response.iset(response.to_string());
100 info.request_timestamp.iset(request_timestamp.to_string());
101 info.response_timestamp.iset(response_timestamp.to_string());
102 info
103 }
104}
105
106#[derive(Default, Inspect)]
107struct ExternalApiCallsWrapper {
108 inspect_node: Node,
109 count: IValue<u64>,
111 calls: ManagedInspectMap<ManagedInspectQueue<ExternalApiCallInfo>>,
113 event_counts: ManagedInspectMap<IValue<u64>>,
115}
116
117pub(crate) struct ExternalApiInspectAgent {
120 api_calls: ManagedInspectMap<ExternalApiCallsWrapper>,
161}
162
163impl ExternalApiInspectAgent {
164 pub(crate) async fn create(context: Context) {
166 Self::create_with_node(
167 context,
168 component::inspector().root().create_child("external_apis"),
169 )
170 .await;
171 }
172
173 async fn create_with_node(context: Context, node: Node) {
175 let (_, message_rx) = context
176 .delegate
177 .create(MessengerType::Broker(Rc::new(move |message| {
178 matches!(
180 message.payload(),
181 service::Payload::Event(EventPayload::Event(Event::ExternalServiceEvent(_)))
182 )
183 })))
184 .await
185 .expect("should receive client");
186
187 let mut agent = ExternalApiInspectAgent {
188 api_calls: ManagedInspectMap::<ExternalApiCallsWrapper>::with_node(node),
189 };
190
191 fasync::Task::local({
192 async move {
193 let _ = &context;
194 let id = fuchsia_trace::Id::new();
195 trace!(id, c"external_api_inspect_agent");
196 let event = message_rx.fuse();
197 let agent_event = context.receptor.fuse();
198 futures::pin_mut!(agent_event, event);
199
200 let mut message_event_fut = event.select_next_some();
201 let mut agent_message_fut = agent_event.select_next_some();
202 loop {
203 futures::select! {
204 message_event = message_event_fut => {
205 trace!(
206 id,
207 c"message_event"
208 );
209 agent.process_message_event(message_event);
210 message_event_fut = event.select_next_some();
211 },
212 agent_message = agent_message_fut => {
213 trace!(
214 id,
215 c"agent_event"
216 );
217 if let MessageEvent::Message(
218 service::Payload::Agent(Payload::Invocation(_)), client)
219 = agent_message {
220 let _ = client.reply(Payload::Complete(Ok(())).into());
223 }
224 agent_message_fut = agent_event.select_next_some();
225 },
226 }
227 }
228 }
229 })
230 .detach();
231 }
232
233 fn process_message_event(&mut self, event: service::message::MessageEvent) {
236 if let Ok((EventPayload::Event(Event::ExternalServiceEvent(external_service_event)), _)) =
237 EventPayload::try_from_with_client(event)
238 {
239 match external_service_event {
240 ExternalServiceEvent::Created(protocol, timestamp) => {
241 let count = self.get_count(protocol) + 1;
242 let info = ExternalApiCallInfo::new("connect", "none", "none", ×tamp);
243 self.add_info(protocol, COMPLETED_CALLS_KEY, "Created", info, count);
244 }
245 ExternalServiceEvent::ApiCall(protocol, request, timestamp) => {
246 let count = self.get_count(protocol) + 1;
247 let info = ExternalApiCallInfo::new(&request, "none", ×tamp, "none");
248 self.add_info(protocol, PENDING_CALLS_KEY, "ApiCall", info, count);
249 }
250 ExternalServiceEvent::ApiResponse(
251 protocol,
252 response,
253 request,
254 request_timestamp,
255 response_timestamp,
256 ) => {
257 let count = self.get_count(protocol) + 1;
258 let info = ExternalApiCallInfo::new(
259 &request,
260 &response,
261 &request_timestamp,
262 &response_timestamp,
263 );
264 self.remove_pending(protocol, &info);
265 self.add_info(protocol, COMPLETED_CALLS_KEY, "ApiResponse", info, count);
266 }
267 ExternalServiceEvent::ApiError(
268 protocol,
269 error,
270 request,
271 request_timestamp,
272 error_timestamp,
273 ) => {
274 let count = self.get_count(protocol) + 1;
275 let info = ExternalApiCallInfo::new(
276 &request,
277 &error,
278 &request_timestamp,
279 &error_timestamp,
280 );
281 self.remove_pending(protocol, &info);
282 self.add_info(protocol, COMPLETED_CALLS_KEY, "ApiError", info, count);
283 }
284 ExternalServiceEvent::Closed(
285 protocol,
286 request,
287 request_timestamp,
288 response_timestamp,
289 ) => {
290 let count = self.get_count(protocol) + 1;
291 let info = ExternalApiCallInfo::new(
292 &request,
293 "closed",
294 &request_timestamp,
295 &response_timestamp,
296 );
297 self.remove_pending(protocol, &info);
298 self.add_info(protocol, COMPLETED_CALLS_KEY, "Closed", info, count);
299 }
300 }
301 }
302 }
303
304 fn get_count(&mut self, protocol: &str) -> u64 {
307 self.ensure_protocol_exists(protocol);
308 *self.api_calls.get(protocol).expect("Wrapper should exist").count
309 }
310
311 fn ensure_protocol_exists(&mut self, protocol: &str) {
314 let _ = self
315 .api_calls
316 .get_or_insert_with(protocol.to_string(), ExternalApiCallsWrapper::default);
317 }
318
319 fn ensure_queue_exists(&mut self, protocol: &str, queue_key: &'static str, queue_size: usize) {
323 self.ensure_protocol_exists(protocol);
324
325 let protocol_map = self.api_calls.get_mut(protocol).expect("Protocol entry should exist");
326 let _ = protocol_map.calls.get_or_insert_with(queue_key.to_string(), || {
327 ManagedInspectQueue::<ExternalApiCallInfo>::new(queue_size)
328 });
329 }
330
331 fn add_info(
334 &mut self,
335 protocol: &str,
336 queue_key: &'static str,
337 event_type: &str,
338 info: ExternalApiCallInfo,
339 count: u64,
340 ) {
341 self.ensure_queue_exists(
342 protocol,
343 queue_key,
344 if queue_key == COMPLETED_CALLS_KEY { MAX_COMPLETED_CALLS } else { MAX_PENDING_CALLS },
345 );
346 let wrapper = self.api_calls.get_mut(protocol).expect("Protocol entry should exist");
347 {
348 let mut wrapper_guard = wrapper.count.as_mut();
349 *wrapper_guard += 1;
350 }
351 let event_count =
352 wrapper.event_counts.get_or_insert_with(event_type.to_string(), || IValue::new(0));
353 {
354 let mut event_count_guard = event_count.as_mut();
355 *event_count_guard += 1;
356 }
357
358 let queue = wrapper.calls.get_mut(queue_key).expect("Queue should exist");
359 let key = format!("{count:020}");
360 queue.push(
361 &key,
362 info.with_inspect(queue.inspect_node(), &key)
363 .expect("Failed to create ExternalApiCallInfo node"),
364 );
365 }
366
367 fn remove_pending(&mut self, protocol: &str, info: &ExternalApiCallInfo) {
371 let wrapper = self.api_calls.get_mut(protocol).expect("Protocol entry should exist");
372 let pending_queue =
373 wrapper.calls.get_mut(PENDING_CALLS_KEY).expect("Pending queue should exist");
374 let req_timestamp = &*info.request_timestamp;
375 pending_queue.retain(|pending| &*pending.request_timestamp != req_timestamp);
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382 use crate::message::base::Audience;
383
384 use diagnostics_assertions::assert_data_tree;
385 use fuchsia_inspect::Inspector;
386 use std::collections::HashSet;
387
388 const MOCK_PROTOCOL_NAME: &str = "fuchsia.external.FakeAPI";
389
390 struct RequestProcessor {
394 delegate: service::message::Delegate,
395 }
396
397 impl RequestProcessor {
398 fn new(delegate: service::message::Delegate) -> Self {
399 RequestProcessor { delegate }
400 }
401
402 async fn send_and_receive(&self, external_api_event: ExternalServiceEvent) {
403 let (messenger, _) =
404 self.delegate.create(MessengerType::Unbound).await.expect("should be created");
405
406 let (_, mut receptor) =
407 self.delegate.create(MessengerType::Unbound).await.expect("should be created");
408
409 let _ = messenger.message(
410 service::Payload::Event(EventPayload::Event(Event::ExternalServiceEvent(
411 external_api_event,
412 ))),
413 Audience::Broadcast,
414 );
415
416 let _ = receptor.next_payload().await;
417 }
418 }
419
420 async fn create_context() -> Context {
421 Context::new(
422 service::MessageHub::create_hub()
423 .create(MessengerType::Unbound)
424 .await
425 .expect("should be present")
426 .1,
427 service::MessageHub::create_hub(),
428 HashSet::new(),
429 )
430 .await
431 }
432
433 #[fuchsia::test(allow_stalls = false)]
434 async fn test_inspect_create_connection() {
435 let inspector = Inspector::default();
436 let inspect_node = inspector.root().create_child("external_apis");
437 let context = create_context().await;
438
439 let request_processor = RequestProcessor::new(context.delegate.clone());
440
441 ExternalApiInspectAgent::create_with_node(context, inspect_node).await;
442
443 let connection_created_event =
444 ExternalServiceEvent::Created(MOCK_PROTOCOL_NAME, "0.000000".into());
445
446 request_processor.send_and_receive(connection_created_event.clone()).await;
447
448 assert_data_tree!(inspector, root: {
449 external_apis: {
450 "fuchsia.external.FakeAPI": {
451 "calls": {
452 "completed_calls": {
453 "00000000000000000001": {
454 request: "connect",
455 response: "none",
456 request_timestamp: "none",
457 response_timestamp: "0.000000",
458 },
459 },
460 },
461 "count": 1u64,
462 "event_counts": {
463 "Created": 1u64,
464 },
465 },
466 },
467 });
468 }
469
470 #[fuchsia::test(allow_stalls = false)]
471 async fn test_inspect_pending() {
472 let inspector = Inspector::default();
473 let inspect_node = inspector.root().create_child("external_apis");
474 let context = create_context().await;
475
476 let request_processor = RequestProcessor::new(context.delegate.clone());
477
478 ExternalApiInspectAgent::create_with_node(context, inspect_node).await;
479
480 let api_call_event = ExternalServiceEvent::ApiCall(
481 MOCK_PROTOCOL_NAME,
482 "set_manual_brightness(0.6)".into(),
483 "0.000000".into(),
484 );
485
486 request_processor.send_and_receive(api_call_event.clone()).await;
487
488 assert_data_tree!(inspector, root: {
489 external_apis: {
490 "fuchsia.external.FakeAPI": {
491 "calls": {
492 "pending_calls": {
493 "00000000000000000001": {
494 request: "set_manual_brightness(0.6)",
495 response: "none",
496 request_timestamp: "0.000000",
497 response_timestamp: "none",
498 },
499 },
500 },
501 "count": 1u64,
502 "event_counts": {
503 "ApiCall": 1u64,
504 },
505 },
506 },
507 });
508 }
509
510 #[fuchsia::test(allow_stalls = false)]
511 async fn test_inspect_success_response() {
512 let inspector = Inspector::default();
513 let inspect_node = inspector.root().create_child("external_apis");
514 let context = create_context().await;
515
516 let request_processor = RequestProcessor::new(context.delegate.clone());
517
518 ExternalApiInspectAgent::create_with_node(context, inspect_node).await;
519
520 let api_call_event = ExternalServiceEvent::ApiCall(
521 MOCK_PROTOCOL_NAME,
522 "set_manual_brightness(0.6)".into(),
523 "0.000000".into(),
524 );
525 let api_response_event = ExternalServiceEvent::ApiResponse(
526 MOCK_PROTOCOL_NAME,
527 "Ok(None)".into(),
528 "set_manual_brightness(0.6)".into(),
529 "0.000000".into(),
530 "0.129987".into(),
531 );
532
533 request_processor.send_and_receive(api_call_event.clone()).await;
534 assert_data_tree!(inspector, root: {
535 external_apis: {
536 "fuchsia.external.FakeAPI": {
537 "calls": {
538 "pending_calls": {
539 "00000000000000000001": {
540 request: "set_manual_brightness(0.6)",
541 response: "none",
542 request_timestamp: "0.000000",
543 response_timestamp: "none",
544 },
545 },
546 },
547 "count": 1u64,
548 "event_counts": {
549 "ApiCall": 1u64,
550 },
551 },
552 },
553 });
554
555 request_processor.send_and_receive(api_response_event.clone()).await;
556
557 assert_data_tree!(inspector, root: {
558 external_apis: {
559 "fuchsia.external.FakeAPI": {
560 "calls": {
561 "pending_calls": {},
562 "completed_calls": {
563 "00000000000000000002": {
564 request: "set_manual_brightness(0.6)",
565 response: "Ok(None)",
566 request_timestamp: "0.000000",
567 response_timestamp: "0.129987",
568 },
569 },
570 },
571 "count": 2u64,
572 "event_counts": {
573 "ApiCall": 1u64,
574 "ApiResponse": 1u64,
575 },
576 },
577 },
578 });
579 }
580
581 #[fuchsia::test(allow_stalls = false)]
582 async fn test_inspect_error() {
583 let inspector = Inspector::default();
584 let inspect_node = inspector.root().create_child("external_apis");
585 let context = create_context().await;
586
587 let request_processor = RequestProcessor::new(context.delegate.clone());
588
589 ExternalApiInspectAgent::create_with_node(context, inspect_node).await;
590
591 let api_call_event = ExternalServiceEvent::ApiCall(
592 MOCK_PROTOCOL_NAME,
593 "set_manual_brightness(0.6)".into(),
594 "0.000000".into(),
595 );
596 let error_event = ExternalServiceEvent::ApiError(
597 MOCK_PROTOCOL_NAME,
598 "Err(INTERNAL_ERROR)".into(),
599 "set_manual_brightness(0.6)".into(),
600 "0.000000".into(),
601 "0.129987".into(),
602 );
603
604 request_processor.send_and_receive(api_call_event.clone()).await;
605
606 assert_data_tree!(inspector, root: {
607 external_apis: {
608 "fuchsia.external.FakeAPI": {
609 "calls": {
610 "pending_calls": {
611 "00000000000000000001": {
612 request: "set_manual_brightness(0.6)",
613 response: "none",
614 request_timestamp: "0.000000",
615 response_timestamp: "none",
616 },
617 },
618 },
619 "count": 1u64,
620 "event_counts": {
621 "ApiCall": 1u64,
622 },
623 },
624 },
625 });
626
627 request_processor.send_and_receive(error_event.clone()).await;
628
629 assert_data_tree!(inspector, root: {
630 external_apis: {
631 "fuchsia.external.FakeAPI": {
632 "calls": {
633 "pending_calls": {},
634 "completed_calls": {
635 "00000000000000000002": {
636 request: "set_manual_brightness(0.6)",
637 response: "Err(INTERNAL_ERROR)",
638 request_timestamp: "0.000000",
639 response_timestamp: "0.129987",
640 },
641 },
642 },
643 "count": 2u64,
644 "event_counts": {
645 "ApiCall": 1u64,
646 "ApiError": 1u64,
647 },
648 },
649 },
650 });
651 }
652
653 #[fuchsia::test(allow_stalls = false)]
654 async fn test_inspect_channel_closed() {
655 let inspector = Inspector::default();
656 let inspect_node = inspector.root().create_child("external_apis");
657 let context = create_context().await;
658
659 let request_processor = RequestProcessor::new(context.delegate.clone());
660
661 ExternalApiInspectAgent::create_with_node(context, inspect_node).await;
662
663 let api_call_event = ExternalServiceEvent::ApiCall(
664 MOCK_PROTOCOL_NAME,
665 "set_manual_brightness(0.6)".into(),
666 "0.000000".into(),
667 );
668 let closed_event = ExternalServiceEvent::Closed(
669 MOCK_PROTOCOL_NAME,
670 "set_manual_brightness(0.6)".into(),
671 "0.000000".into(),
672 "0.129987".into(),
673 );
674
675 request_processor.send_and_receive(api_call_event.clone()).await;
676
677 assert_data_tree!(inspector, root: {
678 external_apis: {
679 "fuchsia.external.FakeAPI": {
680 "calls": {
681 "pending_calls": {
682 "00000000000000000001": {
683 request: "set_manual_brightness(0.6)",
684 response: "none",
685 request_timestamp: "0.000000",
686 response_timestamp: "none",
687 },
688 },
689 },
690 "count": 1u64,
691 "event_counts": {
692 "ApiCall": 1u64,
693 },
694 },
695 },
696 });
697
698 request_processor.send_and_receive(closed_event.clone()).await;
699
700 assert_data_tree!(inspector, root: {
701 external_apis: {
702 "fuchsia.external.FakeAPI": {
703 "calls": {
704 "pending_calls": {},
705 "completed_calls": {
706 "00000000000000000002": {
707 request: "set_manual_brightness(0.6)",
708 response: "closed",
709 request_timestamp: "0.000000",
710 response_timestamp: "0.129987",
711 },
712 },
713 },
714 "count": 2u64,
715 "event_counts": {
716 "ApiCall": 1u64,
717 "Closed": 1u64,
718 },
719 },
720 },
721 });
722 }
723
724 #[fuchsia::test(allow_stalls = false)]
725 async fn test_inspect_multiple_requests() {
726 let inspector = Inspector::default();
727 let inspect_node = inspector.root().create_child("external_apis");
728 let context = create_context().await;
729
730 let request_processor = RequestProcessor::new(context.delegate.clone());
731
732 ExternalApiInspectAgent::create_with_node(context, inspect_node).await;
733
734 let api_call_event = ExternalServiceEvent::ApiCall(
735 MOCK_PROTOCOL_NAME,
736 "set_manual_brightness(0.6)".into(),
737 "0.000000".into(),
738 );
739 let api_response_event = ExternalServiceEvent::ApiResponse(
740 MOCK_PROTOCOL_NAME,
741 "Ok(None)".into(),
742 "set_manual_brightness(0.6)".into(),
743 "0.000000".into(),
744 "0.129987".into(),
745 );
746
747 let api_call_event_2 = ExternalServiceEvent::ApiCall(
748 MOCK_PROTOCOL_NAME,
749 "set_manual_brightness(0.7)".into(),
750 "0.139816".into(),
751 );
752 let api_response_event_2 = ExternalServiceEvent::ApiResponse(
753 MOCK_PROTOCOL_NAME,
754 "Ok(None)".into(),
755 "set_manual_brightness(0.7)".into(),
756 "0.139816".into(),
757 "0.141235".into(),
758 );
759
760 request_processor.send_and_receive(api_call_event.clone()).await;
761 assert_data_tree!(inspector, root: {
762 external_apis: {
763 "fuchsia.external.FakeAPI": {
764 "calls": {
765 "pending_calls": {
766 "00000000000000000001": {
767 request: "set_manual_brightness(0.6)",
768 response: "none",
769 request_timestamp: "0.000000",
770 response_timestamp: "none",
771 },
772 },
773 },
774 "count": 1u64,
775 "event_counts": {
776 "ApiCall": 1u64,
777 },
778 },
779 },
780 });
781
782 request_processor.send_and_receive(api_response_event.clone()).await;
783
784 assert_data_tree!(inspector, root: {
785 external_apis: {
786 "fuchsia.external.FakeAPI": {
787 "calls": {
788 "pending_calls": {},
789 "completed_calls": {
790 "00000000000000000002": {
791 request: "set_manual_brightness(0.6)",
792 response: "Ok(None)",
793 request_timestamp: "0.000000",
794 response_timestamp: "0.129987",
795 },
796 },
797 },
798 "count": 2u64,
799 "event_counts": {
800 "ApiCall": 1u64,
801 "ApiResponse": 1u64,
802 },
803 },
804 },
805 });
806
807 request_processor.send_and_receive(api_call_event_2.clone()).await;
808 assert_data_tree!(inspector, root: {
809 external_apis: {
810 "fuchsia.external.FakeAPI": {
811 "calls": {
812 "pending_calls": {
813 "00000000000000000003": {
814 request: "set_manual_brightness(0.7)",
815 response: "none",
816 request_timestamp: "0.139816",
817 response_timestamp: "none",
818 },
819 },
820 "completed_calls": {
821 "00000000000000000002": {
822 request: "set_manual_brightness(0.6)",
823 response: "Ok(None)",
824 request_timestamp: "0.000000",
825 response_timestamp: "0.129987",
826 },
827 },
828 },
829 "count": 3u64,
830 "event_counts": {
831 "ApiCall": 2u64,
832 "ApiResponse": 1u64,
833 },
834 },
835 },
836 });
837
838 request_processor.send_and_receive(api_response_event_2.clone()).await;
839
840 assert_data_tree!(inspector, root: {
841 external_apis: {
842 "fuchsia.external.FakeAPI": {
843 "calls": {
844 "pending_calls": {},
845 "completed_calls": {
846 "00000000000000000002": {
847 request: "set_manual_brightness(0.6)",
848 response: "Ok(None)",
849 request_timestamp: "0.000000",
850 response_timestamp: "0.129987",
851 },
852 "00000000000000000004": {
853 request: "set_manual_brightness(0.7)",
854 response: "Ok(None)",
855 request_timestamp: "0.139816",
856 response_timestamp: "0.141235",
857 },
858 },
859 },
860 "count": 4u64,
861 "event_counts": {
862 "ApiCall": 2u64,
863 "ApiResponse": 2u64,
864 },
865 },
866 },
867 });
868 }
869}