1#![deny(missing_docs)]
6
7use async_stream::stream;
11use diagnostics_data::{DiagnosticsData, LogsData};
12#[cfg(fuchsia_api_level_less_than = "HEAD")]
13use diagnostics_message as _;
14#[cfg(fuchsia_api_level_at_least = "HEAD")]
15use diagnostics_message::{RustMessageFormatter, from_extended_record};
16use fidl_fuchsia_diagnostics::{
17 ArchiveAccessorMarker, ArchiveAccessorProxy, BatchIteratorMarker, BatchIteratorProxy,
18 ClientSelectorConfiguration, Format, FormattedContent, PerformanceConfiguration, ReaderError,
19 Selector, SelectorArgument, StreamMode, StreamParameters,
20};
21use fuchsia_async::{self as fasync, DurationExt, TimeoutExt};
22use fuchsia_component::client;
23#[cfg(fuchsia_api_level_at_least = "HEAD")]
24use fuchsia_sync::Mutex;
25use futures::channel::mpsc;
26use futures::prelude::*;
27use futures::sink::SinkExt;
28use futures::stream::FusedStream;
29use pin_project::pin_project;
30use serde::Deserialize;
31use std::future::ready;
32use std::marker::PhantomData;
33use std::pin::Pin;
34use std::sync::Arc;
35use std::task::{Context, Poll};
36use thiserror::Error;
37use zx::{self as zx, MonotonicDuration};
38
39pub type LogsArchiveReader = ArchiveReader<Logs>;
41
42pub type InspectArchiveReader = ArchiveReader<Inspect>;
44
45pub use diagnostics_data::{Data, Inspect, Logs, Severity};
46pub use diagnostics_hierarchy::{DiagnosticsHierarchy, Property, hierarchy};
47
48const RETRY_DELAY_MS: i64 = 300;
49
50#[cfg(fuchsia_api_level_at_least = "HEAD")]
51const FORMAT: Format = Format::Cbor;
52#[cfg(fuchsia_api_level_less_than = "HEAD")]
53const FORMAT: Format = Format::Json;
54
55#[derive(Debug, Error)]
57pub enum Error {
58 #[error("Failed to connect to the archive accessor")]
60 ConnectToArchive(#[source] anyhow::Error),
61
62 #[error("Failed to create the BatchIterator channel ends")]
64 CreateIteratorProxy(#[source] fidl::Error),
65
66 #[error("Failed to stream diagnostics from the accessor")]
68 StreamDiagnostics(#[source] fidl::Error),
69
70 #[error("Failed to call iterator server")]
72 GetNextCall(#[source] fidl::Error),
73
74 #[error("Received error from the GetNext response: {0:?}")]
76 GetNextReaderError(ReaderError),
77
78 #[error("Failed to read json received")]
80 ReadJson(#[source] serde_json::Error),
81
82 #[cfg(fuchsia_api_level_at_least = "HEAD")]
84 #[error("Failed to read cbor received")]
85 ReadCbor(#[source] anyhow::Error),
86
87 #[error("Failed to parse the diagnostics data from the json received")]
89 ParseDiagnosticsData(#[source] serde_json::Error),
90
91 #[error("Failed to read vmo from the response")]
93 ReadVmo(#[source] zx::Status),
94}
95
96pub struct ComponentSelector {
98 moniker: Vec<String>,
99 tree_selectors: Vec<String>,
100}
101
102impl ComponentSelector {
103 pub fn new(moniker: Vec<String>) -> Self {
108 Self { moniker, tree_selectors: Vec::new() }
109 }
110
111 pub fn with_tree_selector(mut self, tree_selector: impl Into<String>) -> Self {
113 self.tree_selectors.push(tree_selector.into());
114 self
115 }
116
117 fn moniker_str(&self) -> String {
118 self.moniker.join("/")
119 }
120}
121
122pub trait ToSelectorArguments {
124 fn to_selector_arguments(self) -> Box<dyn Iterator<Item = SelectorArgument>>;
126}
127
128pub trait ToComponentSelectorArguments {
130 fn to_component_selector_arguments(self) -> ComponentSelector;
132}
133
134impl ToComponentSelectorArguments for &str {
135 fn to_component_selector_arguments(self) -> ComponentSelector {
136 if self.contains("\\:") {
137 ComponentSelector::new(self.split("/").map(|value| value.to_string()).collect())
139 } else {
140 ComponentSelector::new(
142 selectors::sanitize_moniker_for_selectors(self)
143 .split("/")
144 .map(|value| value.to_string())
145 .collect(),
146 )
147 .with_tree_selector("[...]root")
148 }
149 }
150}
151
152impl ToComponentSelectorArguments for String {
153 fn to_component_selector_arguments(self) -> ComponentSelector {
154 self.as_str().to_component_selector_arguments()
155 }
156}
157
158impl ToComponentSelectorArguments for ComponentSelector {
159 fn to_component_selector_arguments(self) -> ComponentSelector {
160 self
161 }
162}
163
164impl ToSelectorArguments for String {
165 fn to_selector_arguments(self) -> Box<dyn Iterator<Item = SelectorArgument>> {
166 Box::new([SelectorArgument::RawSelector(self)].into_iter())
167 }
168}
169
170impl ToSelectorArguments for &str {
171 fn to_selector_arguments(self) -> Box<dyn Iterator<Item = SelectorArgument>> {
172 Box::new([SelectorArgument::RawSelector(self.to_string())].into_iter())
173 }
174}
175
176impl ToSelectorArguments for ComponentSelector {
177 fn to_selector_arguments(self) -> Box<dyn Iterator<Item = SelectorArgument>> {
178 let moniker = self.moniker_str();
179 if self.tree_selectors.is_empty() {
181 Box::new([SelectorArgument::RawSelector(format!("{moniker}:root"))].into_iter())
182 } else {
183 Box::new(
184 self.tree_selectors
185 .into_iter()
186 .map(move |s| SelectorArgument::RawSelector(format!("{moniker}:{s}"))),
187 )
188 }
189 }
190}
191
192impl ToSelectorArguments for Selector {
193 fn to_selector_arguments(self) -> Box<dyn Iterator<Item = SelectorArgument>> {
194 Box::new([SelectorArgument::StructuredSelector(self)].into_iter())
195 }
196}
197
198pub trait SerializableValue: private::Sealed {
200 const FORMAT_OF_VALUE: Format;
202}
203
204pub trait CheckResponse: private::Sealed {
206 fn has_payload(&self) -> bool;
208}
209
210mod private {
214 pub trait Sealed {}
215}
216impl private::Sealed for serde_json::Value {}
217impl private::Sealed for ciborium::Value {}
218impl<D: DiagnosticsData> private::Sealed for Data<D> {}
219
220impl<D: DiagnosticsData> CheckResponse for Data<D> {
221 fn has_payload(&self) -> bool {
222 self.payload.is_some()
223 }
224}
225
226impl SerializableValue for serde_json::Value {
227 const FORMAT_OF_VALUE: Format = Format::Json;
228}
229
230impl CheckResponse for serde_json::Value {
231 fn has_payload(&self) -> bool {
232 match self {
233 serde_json::Value::Object(obj) => {
234 obj.get("payload").map(|p| !matches!(p, serde_json::Value::Null)).is_some()
235 }
236 _ => false,
237 }
238 }
239}
240
241#[cfg(fuchsia_api_level_at_least = "HEAD")]
242impl SerializableValue for ciborium::Value {
243 const FORMAT_OF_VALUE: Format = Format::Cbor;
244}
245
246impl CheckResponse for ciborium::Value {
247 fn has_payload(&self) -> bool {
248 match self {
249 ciborium::Value::Map(m) => {
250 let payload_key = ciborium::Value::Text("payload".into());
251 m.iter().any(|(key, _)| *key == payload_key)
252 }
253 _ => false,
254 }
255 }
256}
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
260pub enum RetryConfig {
261 MinSchemaCount(usize),
265}
266
267impl RetryConfig {
268 pub fn always() -> Self {
270 Self::MinSchemaCount(1)
271 }
272
273 pub fn never() -> Self {
275 Self::MinSchemaCount(0)
276 }
277
278 fn should_retry(&self, result_count: usize) -> bool {
280 match self {
281 Self::MinSchemaCount(min) => *min > result_count,
282 }
283 }
284}
285
286pub trait DiagnosticsDataType: private::Sealed {}
288
289impl private::Sealed for Logs {}
290
291impl private::Sealed for Inspect {}
292
293impl DiagnosticsDataType for Logs {}
294
295impl DiagnosticsDataType for Inspect {}
296
297pub struct ArchiveReader<T> {
300 archive: Option<ArchiveAccessorProxy>,
301 selectors: Vec<SelectorArgument>,
302 retry_config: RetryConfig,
303 timeout: Option<MonotonicDuration>,
304 batch_retrieval_timeout_seconds: Option<i64>,
305 max_aggregated_content_size_bytes: Option<u64>,
306 format: Option<Format>,
307 _phantom: PhantomData<T>,
308}
309
310impl<T: DiagnosticsDataType> ArchiveReader<T> {
311 pub fn with_archive(&mut self, archive: ArchiveAccessorProxy) -> &mut Self {
315 self.archive = Some(archive);
316 self
317 }
318
319 pub fn with_minimum_schema_count(&mut self, minimum_schema_count: usize) -> &mut Self {
322 self.retry_config = RetryConfig::MinSchemaCount(minimum_schema_count);
323 self
324 }
325
326 pub fn retry(&mut self, config: RetryConfig) -> &mut Self {
328 self.retry_config = config;
329 self
330 }
331
332 pub fn with_timeout(&mut self, duration: MonotonicDuration) -> &mut Self {
335 self.timeout = Some(duration);
336 self
337 }
338
339 pub fn select_all_for_component(
343 &mut self,
344 component: impl ToComponentSelectorArguments,
345 ) -> &mut Self {
346 self.selectors.extend(component.to_component_selector_arguments().to_selector_arguments());
347 self
348 }
349
350 async fn snapshot_shared<D>(&self, format: Format) -> Result<Vec<Data<D>>, Error>
352 where
353 D: DiagnosticsData + 'static,
354 {
355 let data_future = self.snapshot_inner::<D, Data<D>>(format);
356 let data = match self.timeout {
357 Some(timeout) => data_future.on_timeout(timeout.after_now(), || Ok(Vec::new())).await?,
358 None => data_future.await?,
359 };
360 Ok(data)
361 }
362
363 async fn snapshot_inner<D, Y>(&self, format: Format) -> Result<Vec<Y>, Error>
364 where
365 D: DiagnosticsData,
366 Y: for<'a> Deserialize<'a> + CheckResponse + Send + 'static,
367 {
368 loop {
369 let iterator = self.batch_iterator::<D>(StreamMode::Snapshot, format)?;
370 let result = drain_batch_iterator::<Y>(Arc::new(iterator))
371 .filter_map(|value| ready(value.ok()))
372 .collect::<Vec<_>>()
373 .await;
374
375 if self.retry_config.should_retry(result.len()) {
376 fasync::Timer::new(fasync::MonotonicInstant::after(
377 zx::MonotonicDuration::from_millis(RETRY_DELAY_MS),
378 ))
379 .await;
380 } else {
381 return Ok(result);
382 }
383 }
384 }
385
386 fn batch_iterator<D>(
387 &self,
388 mode: StreamMode,
389 format: Format,
390 ) -> Result<BatchIteratorProxy, Error>
391 where
392 D: DiagnosticsData,
393 {
394 let archive = match &self.archive {
395 Some(archive) => archive.clone(),
396 None => client::connect_to_protocol::<ArchiveAccessorMarker>()
397 .map_err(Error::ConnectToArchive)?,
398 };
399
400 let (iterator, server_end) = fidl::endpoints::create_proxy::<BatchIteratorMarker>();
401 let stream_parameters = StreamParameters {
402 stream_mode: Some(mode),
403 data_type: Some(D::DATA_TYPE),
404 format: Some(format),
405 client_selector_configuration: if self.selectors.is_empty() {
406 Some(ClientSelectorConfiguration::SelectAll(true))
407 } else {
408 Some(ClientSelectorConfiguration::Selectors(self.selectors.to_vec()))
409 },
410 performance_configuration: Some(PerformanceConfiguration {
411 max_aggregate_content_size_bytes: self.max_aggregated_content_size_bytes,
412 batch_retrieval_timeout_seconds: self.batch_retrieval_timeout_seconds,
413 ..Default::default()
414 }),
415 ..Default::default()
416 };
417
418 archive
419 .stream_diagnostics(&stream_parameters, server_end)
420 .map_err(Error::StreamDiagnostics)?;
421 Ok(iterator)
422 }
423}
424
425impl ArchiveReader<Logs> {
426 pub fn logs() -> Self {
428 ArchiveReader::<Logs> {
429 timeout: None,
430 format: None,
431 selectors: vec![],
432 retry_config: RetryConfig::always(),
433 archive: None,
434 batch_retrieval_timeout_seconds: None,
435 max_aggregated_content_size_bytes: None,
436 _phantom: PhantomData,
437 }
438 }
439
440 #[doc(hidden)]
441 pub fn with_format(&mut self, format: Format) -> &mut Self {
442 self.format = Some(format);
443 self
444 }
445
446 #[inline]
447 fn format(&self) -> Format {
448 match self.format {
449 Some(f) => f,
450 None => {
451 #[cfg(fuchsia_api_level_at_least = "HEAD")]
452 let ret = Format::LegacyFxt;
453 #[cfg(fuchsia_api_level_less_than = "HEAD")]
454 let ret = Format::Json;
455 ret
456 }
457 }
458 }
459
460 pub async fn snapshot(&self) -> Result<Vec<Data<Logs>>, Error> {
462 let fut = async {
463 loop {
464 let iterator = self.batch_iterator::<Logs>(StreamMode::Snapshot, self.format())?;
465 let result = drain_batch_iterator_for_logs(Arc::new(iterator), Some(self.format()))
466 .filter_map(|value| ready(value.ok()))
467 .collect::<Vec<_>>()
468 .await;
469 if self.retry_config.should_retry(result.len()) {
470 fasync::Timer::new(fasync::MonotonicInstant::after(
471 zx::MonotonicDuration::from_millis(RETRY_DELAY_MS),
472 ))
473 .await;
474 } else {
475 return Ok(result);
476 }
477 }
478 };
479 match self.timeout {
480 Some(timeout) => fut.on_timeout(timeout.after_now(), || Ok(Vec::new())).await,
481 None => fut.await,
482 }
483 }
484
485 pub fn snapshot_then_subscribe(&self) -> Result<Subscription, Error> {
488 let iterator =
489 self.batch_iterator::<Logs>(StreamMode::SnapshotThenSubscribe, self.format())?;
490 Ok(Subscription::new_with_format(iterator, self.format()))
491 }
492}
493
494impl ArchiveReader<Inspect> {
495 pub fn inspect() -> Self {
497 ArchiveReader::<Inspect> {
498 timeout: None,
499 format: None,
500 selectors: vec![],
501 retry_config: RetryConfig::always(),
502 archive: None,
503 batch_retrieval_timeout_seconds: None,
504 max_aggregated_content_size_bytes: None,
505 _phantom: PhantomData,
506 }
507 }
508
509 pub fn with_batch_retrieval_timeout_seconds(&mut self, timeout: i64) -> &mut Self {
512 self.batch_retrieval_timeout_seconds = Some(timeout);
513 self
514 }
515
516 pub fn with_aggregated_result_bytes_limit(&mut self, limit_bytes: u64) -> &mut Self {
518 self.max_aggregated_content_size_bytes = Some(limit_bytes);
519 self
520 }
521
522 pub async fn snapshot_raw<T>(&self) -> Result<T, Error>
527 where
528 T: for<'a> Deserialize<'a>
529 + SerializableValue
530 + From<Vec<T>>
531 + CheckResponse
532 + 'static
533 + Send,
534 {
535 let data_future = self.snapshot_inner::<Inspect, T>(T::FORMAT_OF_VALUE);
536 let data = match self.timeout {
537 Some(timeout) => data_future.on_timeout(timeout.after_now(), || Ok(Vec::new())).await?,
538 None => data_future.await?,
539 };
540 Ok(T::from(data))
541 }
542
543 pub fn add_selectors<T, S>(&mut self, selectors: T) -> &mut Self
546 where
547 T: Iterator<Item = S>,
548 S: ToSelectorArguments,
549 {
550 for selector in selectors {
551 self.add_selector(selector);
552 }
553 self
554 }
555
556 pub fn add_selector(&mut self, selector: impl ToSelectorArguments) -> &mut Self {
558 self.selectors.extend(selector.to_selector_arguments());
559 self
560 }
561
562 pub fn with_format(&mut self, format: Format) -> &mut Self {
564 self.format = Some(format);
565 self
566 }
567
568 #[inline]
569 fn format(&self) -> Format {
570 match self.format {
571 Some(f) => f,
572 None => FORMAT,
573 }
574 }
575
576 pub async fn snapshot(&self) -> Result<Vec<Data<Inspect>>, Error> {
578 self.snapshot_shared::<Inspect>(self.format()).await
579 }
580}
581
582#[derive(Debug, Deserialize)]
583#[serde(untagged)]
584enum OneOrMany<T> {
585 Many(Vec<T>),
586 One(T),
587}
588
589fn stream_batch<T>(
590 iterator: Arc<BatchIteratorProxy>,
591 process_content: impl Fn(FormattedContent) -> Result<OneOrMany<T>, Error>,
592) -> impl Stream<Item = Result<T, Error>>
593where
594 T: for<'a> Deserialize<'a> + Send + 'static,
595{
596 stream! {
597 loop {
598 let next_batch = iterator
599 .get_next()
600 .await
601 .map_err(Error::GetNextCall)?
602 .map_err(Error::GetNextReaderError)?;
603 if next_batch.is_empty() {
604 return;
606 }
607 for formatted_content in next_batch {
608 let output = process_content(formatted_content)?;
609 match output {
610 OneOrMany::One(data) => yield Ok(data),
611 OneOrMany::Many(datas) => {
612 for data in datas {
613 yield Ok(data);
614 }
615 }
616 }
617 }
618 }
619 }
620}
621
622pub fn drain_batch_iterator<T>(
624 iterator: Arc<BatchIteratorProxy>,
625) -> impl Stream<Item = Result<T, Error>>
626where
627 T: for<'a> Deserialize<'a> + Send + 'static,
628{
629 stream_batch(iterator, |formatted_content| match formatted_content {
630 FormattedContent::Json(data) => {
631 let mut buf = vec![0; data.size as usize];
632 data.vmo.read(&mut buf, 0).map_err(Error::ReadVmo)?;
633 serde_json::from_slice(&buf).map_err(Error::ReadJson)
634 }
635 #[cfg(fuchsia_api_level_at_least = "HEAD")]
636 FormattedContent::Cbor(vmo) => {
637 let mut buf = vec![0; vmo.get_content_size().expect("Always returns Ok") as usize];
638 vmo.read(&mut buf, 0).map_err(Error::ReadVmo)?;
639 Ok(ciborium::from_reader(buf.as_slice()).map_err(|err| Error::ReadCbor(err.into()))?)
640 }
641 #[cfg(fuchsia_api_level_at_least = "HEAD")]
642 FormattedContent::Fxt(_) => unreachable!("We never expect FXT for Inspect"),
643 FormattedContent::__SourceBreaking { unknown_ordinal: _ } => {
644 unreachable!("Received unrecognized FIDL message")
645 }
646 })
647}
648
649fn drain_batch_iterator_for_logs(
650 iterator: Arc<BatchIteratorProxy>,
651 _format: Option<Format>,
652) -> impl Stream<Item = Result<LogsData, Error>> {
653 #[cfg(fuchsia_api_level_at_least = "HEAD")]
654 let parser = Arc::new(Mutex::new(diagnostics_message::MessageParser::default()));
655 stream_batch::<LogsData>(iterator, move |formatted_content| match formatted_content {
656 FormattedContent::Json(data) => {
657 let mut buf = vec![0; data.size as usize];
658 data.vmo.read(&mut buf, 0).map_err(Error::ReadVmo)?;
659 serde_json::from_slice(&buf).map_err(Error::ReadJson)
660 }
661 #[cfg(fuchsia_api_level_at_least = "HEAD")]
662 FormattedContent::Fxt(vmo) => {
663 let mut buf = vec![0; vmo.get_content_size().expect("Always returns Ok") as usize];
664 vmo.read(&mut buf, 0).map_err(Error::ReadVmo)?;
665 let mut current_slice: &[u8] = &buf;
666 let mut items = vec![];
667 let mut parser = parser.lock();
668
669 while !current_slice.is_empty() {
670 if _format == Some(Format::Fxt) {
671 match parser.parse_next(current_slice, RustMessageFormatter) {
672 Ok((maybe_data, remaining)) => {
673 assert!(remaining.len() < current_slice.len(), "Parser must advance");
674 if let Some(data) = maybe_data {
675 items.push(data);
676 }
677 current_slice = remaining;
678 }
679 Err(_) => {
680 break;
683 }
684 }
685 } else {
686 match from_extended_record(current_slice) {
687 Ok((data, remaining)) => {
688 items.push(data);
689 current_slice = remaining;
690 }
691 Err(_) => {
692 break;
695 }
696 }
697 }
698 }
699 Ok(OneOrMany::Many(items))
700 }
701 #[cfg(fuchsia_api_level_at_least = "HEAD")]
702 FormattedContent::Cbor(_) => unreachable!("We never expect CBOR"),
703 FormattedContent::__SourceBreaking { unknown_ordinal: _ } => {
704 unreachable!("Received unrecognized FIDL message")
705 }
706 })
707}
708
709#[pin_project]
711pub struct Subscription {
712 #[pin]
713 recv: Pin<Box<dyn FusedStream<Item = Result<LogsData, Error>> + Send>>,
714 iterator: Arc<BatchIteratorProxy>,
715}
716
717const DATA_CHANNEL_SIZE: usize = 32;
718const ERROR_CHANNEL_SIZE: usize = 2;
719
720impl Subscription {
721 pub fn new(iterator: BatchIteratorProxy) -> Self {
724 let iterator = Arc::new(iterator);
725 Subscription {
726 recv: Box::pin(drain_batch_iterator_for_logs(iterator.clone(), None).fuse()),
727 iterator,
728 }
729 }
730
731 pub fn new_with_format(iterator: BatchIteratorProxy, format: Format) -> Self {
734 let iterator = Arc::new(iterator);
735 Subscription {
736 recv: Box::pin(drain_batch_iterator_for_logs(iterator.clone(), Some(format)).fuse()),
737 iterator,
738 }
739 }
740
741 pub async fn wait_for_ready(&self) {
743 self.iterator.wait_for_ready().await.expect("doesn't disconnect");
744 }
745
746 pub fn split_streams(mut self) -> (SubscriptionResultsStream<LogsData>, mpsc::Receiver<Error>) {
748 let (mut errors_sender, errors) = mpsc::channel(ERROR_CHANNEL_SIZE);
749 let (mut results_sender, recv) = mpsc::channel(DATA_CHANNEL_SIZE);
750 let _drain_task = fasync::Task::spawn(async move {
751 while let Some(result) = self.next().await {
752 match result {
753 Ok(value) => {
754 if results_sender.send(value).await.is_err() {
755 break;
756 }
757 }
758 Err(e) => {
759 let _ = errors_sender.try_send(e);
762 }
763 };
764 }
765 });
766 (SubscriptionResultsStream { recv, _drain_task }, errors)
767 }
768}
769
770impl Stream for Subscription {
771 type Item = Result<LogsData, Error>;
772
773 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
774 let this = self.project();
775 this.recv.poll_next(cx)
776 }
777}
778
779impl FusedStream for Subscription {
780 fn is_terminated(&self) -> bool {
781 self.recv.is_terminated()
782 }
783}
784
785#[pin_project]
787pub struct SubscriptionResultsStream<T> {
788 #[pin]
789 recv: mpsc::Receiver<T>,
790 _drain_task: fasync::Task<()>,
791}
792
793impl<T> Stream for SubscriptionResultsStream<T>
794where
795 T: for<'a> Deserialize<'a>,
796{
797 type Item = T;
798
799 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
800 let this = self.project();
801 this.recv.poll_next(cx)
802 }
803}
804
805impl<T> FusedStream for SubscriptionResultsStream<T>
806where
807 T: for<'a> Deserialize<'a>,
808{
809 fn is_terminated(&self) -> bool {
810 self.recv.is_terminated()
811 }
812}
813
814#[cfg(test)]
815mod tests {
816 use super::*;
817 use assert_matches::assert_matches;
818 use diagnostics_assertions::assert_data_tree;
819 use diagnostics_log::{Publisher, PublisherOptions};
820 use fidl::endpoints::ServerEnd;
821 use fidl_fuchsia_diagnostics as fdiagnostics;
822 use fuchsia_component_test::{
823 Capability, ChildOptions, RealmBuilder, RealmInstance, Ref, Route,
824 };
825 use futures::TryStreamExt;
826 use log::{error, info};
827
828 const TEST_COMPONENT_URL: &str = "#meta/inspect_test_component.cm";
829
830 struct ComponentOptions {
831 publish_n_trees: u64,
832 }
833
834 async fn start_component(opts: ComponentOptions) -> Result<RealmInstance, anyhow::Error> {
835 let builder = RealmBuilder::new().await?;
836 let test_component = builder
837 .add_child("test_component", TEST_COMPONENT_URL, ChildOptions::new().eager())
838 .await?;
839 builder
840 .add_route(
841 Route::new()
842 .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
843 .from(Ref::parent())
844 .to(&test_component),
845 )
846 .await?;
847 builder.init_mutable_config_to_empty(&test_component).await.unwrap();
848 builder
849 .set_config_value(&test_component, "publish_n_trees", opts.publish_n_trees.into())
850 .await
851 .unwrap();
852 let instance = builder.build().await?;
853 Ok(instance)
854 }
855
856 #[fuchsia::test]
859 async fn inspect_data_for_component() -> Result<(), anyhow::Error> {
860 let instance = start_component(ComponentOptions { publish_n_trees: 1 }).await?;
861 let moniker = format!("realm_builder:{}/test_component", instance.root.child_name());
862 let component_selector = selectors::sanitize_moniker_for_selectors(&moniker);
863 let results = ArchiveReader::inspect()
864 .add_selector(format!("{component_selector}:[...]root"))
865 .snapshot()
866 .await?;
867 assert_eq!(results.len(), 1);
868 assert_data_tree!(results[0].payload.as_ref().unwrap(), root: {
869 "tree-0": 0u64,
870 int: 3u64,
871 "lazy-node": {
872 a: "test",
873 child: {
874 double: 3.25,
875 },
876 }
877 });
878 let lazy_property_selector = Selector {
880 component_selector: Some(fdiagnostics::ComponentSelector {
881 moniker_segments: Some(vec![
882 fdiagnostics::StringSelector::ExactMatch(format!(
883 "realm_builder:{}",
884 instance.root.child_name()
885 )),
886 fdiagnostics::StringSelector::ExactMatch("test_component".into()),
887 ]),
888 ..Default::default()
889 }),
890 tree_selector: Some(fdiagnostics::TreeSelector::PropertySelector(
891 fdiagnostics::PropertySelector {
892 node_path: vec![
893 fdiagnostics::StringSelector::ExactMatch("root".into()),
894 fdiagnostics::StringSelector::ExactMatch("lazy-node".into()),
895 ],
896 target_properties: fdiagnostics::StringSelector::ExactMatch("a".into()),
897 },
898 )),
899 tree_names: Some(fdiagnostics::TreeNames::All(fdiagnostics::All {})),
900 ..Default::default()
901 };
902 let int_property_selector = format!("{component_selector}:[...]root:int");
903 let mut reader = ArchiveReader::inspect();
904 reader.add_selector(int_property_selector).add_selector(lazy_property_selector);
905 let response = reader.snapshot().await?;
906 assert_eq!(response.len(), 1);
907 assert_eq!(response[0].moniker.to_string(), moniker);
908 assert_data_tree!(response[0].payload.as_ref().unwrap(), root: {
909 int: 3u64,
910 "lazy-node": {
911 a: "test"
912 }
913 });
914 Ok(())
915 }
916
917 #[fuchsia::test]
918 async fn select_all_for_moniker() {
919 let instance = start_component(ComponentOptions { publish_n_trees: 1 })
920 .await
921 .expect("component started");
922 let moniker = format!("realm_builder:{}/test_component", instance.root.child_name());
923 let results = ArchiveReader::inspect()
924 .select_all_for_component(moniker)
925 .snapshot()
926 .await
927 .expect("snapshotted");
928 assert_eq!(results.len(), 1);
929 assert_data_tree!(results[0].payload.as_ref().unwrap(), root: {
930 "tree-0": 0u64,
931 int: 3u64,
932 "lazy-node": {
933 a: "test",
934 child: {
935 double: 3.25,
936 },
937 }
938 });
939 }
940
941 #[fuchsia::test]
942 async fn timeout() -> Result<(), anyhow::Error> {
943 let instance = start_component(ComponentOptions { publish_n_trees: 1 }).await?;
944
945 let mut reader = ArchiveReader::inspect();
946 reader
947 .add_selector(format!(
948 "realm_builder\\:{}/test_component:root",
949 instance.root.child_name()
950 ))
951 .with_timeout(zx::MonotonicDuration::from_nanos(0));
952 let result = reader.snapshot().await;
953 assert!(result.unwrap().is_empty());
954 Ok(())
955 }
956
957 #[fuchsia::test]
958 async fn component_selector() {
959 let selector = ComponentSelector::new(vec!["a".to_string()]);
960 assert_eq!(selector.moniker_str(), "a");
961 let arguments: Vec<_> = selector.to_selector_arguments().collect();
962 assert_eq!(arguments, vec![SelectorArgument::RawSelector("a:root".to_string())]);
963
964 let selector =
965 ComponentSelector::new(vec!["b".to_string(), "c".to_string(), "a".to_string()]);
966 assert_eq!(selector.moniker_str(), "b/c/a");
967
968 let selector = selector.with_tree_selector("root/b/c:d").with_tree_selector("root/e:f");
969 let arguments: Vec<_> = selector.to_selector_arguments().collect();
970 assert_eq!(
971 arguments,
972 vec![
973 SelectorArgument::RawSelector("b/c/a:root/b/c:d".into()),
974 SelectorArgument::RawSelector("b/c/a:root/e:f".into()),
975 ]
976 );
977 }
978
979 #[fuchsia::test]
980 async fn custom_archive() {
981 let proxy = spawn_fake_archive(serde_json::json!({
982 "moniker": "moniker",
983 "version": 1,
984 "data_source": "Inspect",
985 "metadata": {
986 "component_url": "component-url",
987 "timestamp": 0,
988 "filename": "filename",
989 },
990 "payload": {
991 "root": {
992 "x": 1,
993 }
994 }
995 }));
996 let result =
997 ArchiveReader::inspect().with_archive(proxy).snapshot().await.expect("got result");
998 assert_eq!(result.len(), 1);
999 assert_data_tree!(result[0].payload.as_ref().unwrap(), root: { x: 1u64 });
1000 }
1001
1002 #[fuchsia::test]
1003 async fn handles_lists_correctly_on_snapshot_raw() {
1004 let value = serde_json::json!({
1005 "moniker": "moniker",
1006 "version": 1,
1007 "data_source": "Inspect",
1008 "metadata": {
1009 "component_url": "component-url",
1010 "timestamp": 0,
1011 "filename": "filename",
1012 },
1013 "payload": {
1014 "root": {
1015 "x": 1,
1016 }
1017 }
1018 });
1019 let proxy = spawn_fake_archive(serde_json::json!([value.clone()]));
1020 let mut reader = ArchiveReader::inspect();
1021 reader.with_archive(proxy);
1022 let json_result = reader.snapshot_raw::<serde_json::Value>().await.expect("got result");
1023 match json_result {
1024 serde_json::Value::Array(values) => {
1025 assert_eq!(values.len(), 1);
1026 assert_eq!(values[0], value);
1027 }
1028 result => panic!("unexpected result: {result:?}"),
1029 }
1030 let cbor_result = reader.snapshot_raw::<ciborium::Value>().await.expect("got result");
1031 match cbor_result {
1032 ciborium::Value::Array(values) => {
1033 assert_eq!(values.len(), 1);
1034 let json_result =
1035 values[0].deserialized::<serde_json::Value>().expect("convert to json");
1036 assert_eq!(json_result, value);
1037 }
1038 result => panic!("unexpected result: {result:?}"),
1039 }
1040 }
1041
1042 #[fuchsia::test(logging = false)]
1043 async fn snapshot_then_subscribe() {
1044 let (_instance, publisher, reader) = init_isolated_logging().await;
1045 let (mut stream, _errors) =
1046 reader.snapshot_then_subscribe().expect("subscribed to logs").split_streams();
1047 publisher.register_logger(None).unwrap();
1048 info!("hello from test");
1049 error!("error from test");
1050 let log = stream.next().await.unwrap();
1051 assert_eq!(log.msg().unwrap(), "hello from test");
1052 let log = stream.next().await.unwrap();
1053 assert_eq!(log.msg().unwrap(), "error from test");
1054 }
1055
1056 #[fuchsia::test]
1057 async fn read_many_trees_with_filtering() {
1058 let instance = start_component(ComponentOptions { publish_n_trees: 2 })
1059 .await
1060 .expect("component started");
1061 let selector = format!(
1062 "realm_builder\\:{}/test_component:[name=tree-0]root",
1063 instance.root.child_name()
1064 );
1065 let results = ArchiveReader::inspect()
1066 .add_selector(selector)
1067 .with_minimum_schema_count(1)
1069 .snapshot()
1070 .await
1071 .expect("snapshotted");
1072 assert_matches!(results.iter().find(|v| v.metadata.name.as_ref() == "tree-1"), None);
1073 let should_have_data =
1074 results.into_iter().find(|v| v.metadata.name.as_ref() == "tree-0").unwrap();
1075 assert_data_tree!(should_have_data.payload.unwrap(), root: contains {
1076 "tree-0": 0u64,
1077 });
1078 }
1079
1080 fn spawn_fake_archive(data_to_send: serde_json::Value) -> fdiagnostics::ArchiveAccessorProxy {
1081 let (proxy, mut stream) =
1082 fidl::endpoints::create_proxy_and_stream::<fdiagnostics::ArchiveAccessorMarker>();
1083 fasync::Task::spawn(async move {
1084 while let Some(request) = stream.try_next().await.expect("stream request") {
1085 match request {
1086 fdiagnostics::ArchiveAccessorRequest::StreamDiagnostics {
1087 result_stream,
1088 ..
1089 } => {
1090 let data = data_to_send.clone();
1091 fasync::Task::spawn(handle_batch_iterator(data, result_stream)).detach();
1092 }
1093 fdiagnostics::ArchiveAccessorRequest::WaitForReady { responder, .. } => {
1094 let _ = responder.send();
1095 }
1096 fdiagnostics::ArchiveAccessorRequest::_UnknownMethod { .. } => {
1097 unreachable!("Unexpected method call");
1098 }
1099 fidl_fuchsia_diagnostics::ArchiveAccessorRequest::StreamDiagnosticsToSocket { .. } => {
1100 unreachable!("Unexpected method call");
1101 },
1102 }
1103 }
1104 })
1105 .detach();
1106 proxy
1107 }
1108
1109 async fn handle_batch_iterator(
1110 data: serde_json::Value,
1111 result_stream: ServerEnd<fdiagnostics::BatchIteratorMarker>,
1112 ) {
1113 let mut called = false;
1114 let mut stream = result_stream.into_stream();
1115 while let Some(req) = stream.try_next().await.expect("stream request") {
1116 match req {
1117 fdiagnostics::BatchIteratorRequest::WaitForReady { responder } => {
1118 let _ = responder.send();
1119 }
1120 fdiagnostics::BatchIteratorRequest::GetNext { responder } => {
1121 if called {
1122 responder.send(Ok(Vec::new())).expect("send response");
1123 continue;
1124 }
1125 called = true;
1126 let content = serde_json::to_string_pretty(&data).expect("json pretty");
1127 let vmo_size = content.len() as u64;
1128 let vmo = zx::Vmo::create(vmo_size).expect("create vmo");
1129 vmo.write(content.as_bytes(), 0).expect("write vmo");
1130 let buffer = fidl_fuchsia_mem::Buffer { vmo, size: vmo_size };
1131 responder
1132 .send(Ok(vec![fdiagnostics::FormattedContent::Json(buffer)]))
1133 .expect("send response");
1134 }
1135 fdiagnostics::BatchIteratorRequest::_UnknownMethod { .. } => {
1136 unreachable!("Unexpected method call");
1137 }
1138 }
1139 }
1140 }
1141
1142 async fn create_realm() -> RealmBuilder {
1143 let builder = RealmBuilder::new().await.expect("create realm builder");
1144 let archivist = builder
1145 .add_child("archivist", "#meta/archivist-for-embedding.cm", ChildOptions::new().eager())
1146 .await
1147 .expect("add child archivist");
1148 builder
1149 .add_route(
1150 Route::new()
1151 .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
1152 .capability(
1153 Capability::protocol_by_name("fuchsia.tracing.provider.Registry")
1154 .optional(),
1155 )
1156 .capability(Capability::event_stream("stopped"))
1157 .capability(Capability::event_stream("capability_requested"))
1158 .from(Ref::parent())
1159 .to(&archivist),
1160 )
1161 .await
1162 .expect("added routes from parent to archivist");
1163 builder
1164 .add_route(
1165 Route::new()
1166 .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
1167 .from(&archivist)
1168 .to(Ref::parent()),
1169 )
1170 .await
1171 .expect("routed LogSink from archivist to parent");
1172 builder
1173 .add_route(
1174 Route::new()
1175 .capability(Capability::protocol_by_name("fuchsia.diagnostics.ArchiveAccessor"))
1176 .from(Ref::dictionary(&archivist, "diagnostics-accessors"))
1177 .to(Ref::parent()),
1178 )
1179 .await
1180 .expect("routed ArchiveAccessor from archivist to parent");
1181 builder
1182 }
1183
1184 async fn init_isolated_logging() -> (RealmInstance, Publisher, ArchiveReader<Logs>) {
1185 let instance = create_realm().await.build().await.unwrap();
1186 let log_sink_client = instance.root.connect_to_protocol_at_exposed_dir().unwrap();
1187 let accessor_proxy = instance.root.connect_to_protocol_at_exposed_dir().unwrap();
1188 let mut reader = ArchiveReader::logs();
1189 reader.with_archive(accessor_proxy);
1190 let options = PublisherOptions::default().use_log_sink(log_sink_client);
1191 let publisher = Publisher::new_async(options).await.unwrap();
1192 (instance, publisher, reader)
1193 }
1194
1195 #[fuchsia::test]
1196 fn retry_config_behavior() {
1197 let config = RetryConfig::MinSchemaCount(1);
1198 let got = 0;
1199
1200 assert!(config.should_retry(got));
1201
1202 let config = RetryConfig::MinSchemaCount(1);
1203 let got = 1;
1204
1205 assert!(!config.should_retry(got));
1206
1207 let config = RetryConfig::MinSchemaCount(1);
1208 let got = 2;
1209
1210 assert!(!config.should_retry(got));
1211
1212 let config = RetryConfig::MinSchemaCount(0);
1213 let got = 1;
1214
1215 assert!(!config.should_retry(got));
1216
1217 let config = RetryConfig::always();
1218 let got = 0;
1219
1220 assert!(config.should_retry(got));
1221
1222 let config = RetryConfig::never();
1223 let got = 0;
1224
1225 assert!(!config.should_retry(got));
1226 }
1227}