inspect_runtime/
service.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Implementation of the `fuchsia.inspect.Tree` protocol server.
6
7use crate::TreeServerSendPreference;
8use anyhow::Error;
9use fidl_fuchsia_inspect::{
10    TreeContent, TreeMarker, TreeNameIteratorRequest, TreeNameIteratorRequestStream, TreeRequest,
11    TreeRequestStream,
12};
13use fidl_fuchsia_mem::Buffer;
14use fuchsia_async as fasync;
15use fuchsia_inspect::reader::ReadableTree;
16use fuchsia_inspect::Inspector;
17use futures::{FutureExt, TryFutureExt, TryStreamExt};
18use log::warn;
19use zx::sys::ZX_CHANNEL_MAX_MSG_BYTES;
20
21/// Runs a server for the `fuchsia.inspect.Tree` protocol. This protocol returns the VMO
22/// associated with the given tree on `get_content` and allows to open linked trees (lazy nodes).
23#[allow(clippy::manual_async_fn)] // required because of recursion
24pub fn handle_request_stream(
25    inspector: Inspector,
26    settings: TreeServerSendPreference,
27    mut stream: TreeRequestStream,
28    scope: fasync::Scope,
29) -> impl futures::Future<Output = Result<(), Error>> + Send {
30    async move {
31        while let Some(request) = stream.try_next().await? {
32            match request {
33                TreeRequest::GetContent { responder } => {
34                    // If freezing fails, full snapshot algo needed on live duplicate
35                    let vmo = match settings {
36                        TreeServerSendPreference::DeepCopy => inspector.copy_vmo(),
37                        TreeServerSendPreference::Live => inspector.duplicate_vmo(),
38                        TreeServerSendPreference::Frozen { ref on_failure } => {
39                            inspector.frozen_vmo_copy().or_else(|| match **on_failure {
40                                TreeServerSendPreference::DeepCopy => inspector.copy_vmo(),
41                                TreeServerSendPreference::Live => inspector.duplicate_vmo(),
42                                _ => None,
43                            })
44                        }
45                    };
46
47                    let buffer_data =
48                        vmo.and_then(|vmo| vmo.get_size().ok().map(|size| (vmo, size)));
49                    let content = TreeContent {
50                        buffer: buffer_data.map(|data| Buffer { vmo: data.0, size: data.1 }),
51                        ..Default::default()
52                    };
53                    responder.send(content)?;
54                }
55                TreeRequest::ListChildNames { tree_iterator, .. } => {
56                    let request_stream = tree_iterator.into_stream();
57                    let inspector = inspector.clone();
58                    scope.spawn(async move {
59                        inspector
60                            .tree_names()
61                            .map_err(|e| anyhow::anyhow!("{e:?}"))
62                            .and_then(|values| {
63                                run_tree_name_iterator_server(values, request_stream)
64                            })
65                            .map_err(|err| {
66                                warn!(err:?; "failed to run tree name iterator server");
67                            })
68                            .map(|_| {})
69                            .await
70                    });
71                }
72                TreeRequest::OpenChild { child_name, tree, .. } => {
73                    let nested = scope.new_child_with_name("nested_tree_server");
74                    let inspector = inspector.clone();
75                    let settings = settings.clone();
76                    let stream = tree.into_stream();
77                    scope.spawn(async move {
78                        inspector
79                            .read_tree(&child_name)
80                            .map_err(|e| anyhow::anyhow!("{e:?}"))
81                            .and_then(|inspector| {
82                                handle_request_stream(inspector, settings, stream, nested)
83                            })
84                            .map_err(|err| {
85                                warn!(err:?; "failed to run `fuchsia.inspect.Tree` server");
86                            })
87                            .map(|_| {})
88                            .await
89                    });
90                }
91                TreeRequest::_UnknownMethod { ordinal, method_type, .. } => {
92                    warn!(ordinal, method_type:?; "Unknown request");
93                }
94            }
95        }
96
97        scope.join().await;
98
99        Ok(())
100    }
101}
102
103/// Spawns a server for the `fuchsia.inspect.Tree` protocol. This protocol returns the VMO
104/// associated with the given tree on `get_content` and allows to open linked trees (lazy nodes).
105///
106/// This version of the function accepts a `TreeRequestStream`, making it suitable for the
107/// recursive calls performed by the `OpenChild` method on `fuchsia.inspect.Tree`.
108/// `spawn_tree_server` is a more ergonomic option for spawning the root tree.
109pub fn spawn_tree_server_with_stream(
110    inspector: Inspector,
111    settings: TreeServerSendPreference,
112    stream: TreeRequestStream,
113    scope: &fasync::ScopeHandle,
114) {
115    scope.spawn(
116        handle_request_stream(
117            inspector,
118            settings,
119            stream,
120            scope.new_child_with_name("tree_server"),
121        )
122        .map(|e| {
123            e.unwrap_or_else(
124                |err: Error| warn!(err:?; "failed to run `fuchsia.inspect.Tree` server"),
125            );
126        }),
127    );
128}
129
130/// Spawns a `fuchsia.inspect.Tree` server and returns the task handling
131/// `fuchsia.inspect.Tree requests and a `ClientEnd` handle to the tree.
132pub fn spawn_tree_server(
133    inspector: Inspector,
134    settings: TreeServerSendPreference,
135    scope: &fasync::ScopeHandle,
136) -> fidl::endpoints::ClientEnd<TreeMarker> {
137    let (tree, server_end) = fidl::endpoints::create_endpoints::<TreeMarker>();
138    spawn_tree_server_with_stream(inspector, settings, server_end.into_stream(), scope);
139    tree
140}
141
142/// Runs a server for the `fuchsia.inspect.TreeNameIterator` protocol. This protocol returns the
143/// given list of values by chunks.
144async fn run_tree_name_iterator_server(
145    values: Vec<String>,
146    mut stream: TreeNameIteratorRequestStream,
147) -> Result<(), anyhow::Error> {
148    let mut values_iter = values.into_iter().peekable();
149    while let Some(request) = stream.try_next().await? {
150        match request {
151            TreeNameIteratorRequest::GetNext { responder } => {
152                let mut bytes_used: usize = 32; // Page overhead of message header + vector
153                let mut result = vec![];
154                loop {
155                    match values_iter.peek() {
156                        None => break,
157                        Some(value) => {
158                            bytes_used += 16; // String overhead
159                            bytes_used += fidl::encoding::round_up_to_align(value.len(), 8);
160                            if bytes_used > ZX_CHANNEL_MAX_MSG_BYTES as usize {
161                                break;
162                            }
163                            result.push(values_iter.next().unwrap());
164                        }
165                    }
166                }
167                if result.is_empty() {
168                    responder.send(&[])?;
169                    return Ok(());
170                }
171                responder.send(&result)?;
172            }
173            TreeNameIteratorRequest::_UnknownMethod { ordinal, method_type, .. } => {
174                warn!(ordinal, method_type:?; "Unknown request");
175            }
176        }
177    }
178    Ok(())
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use diagnostics_assertions::{assert_data_tree, assert_json_diff};
185    use fidl_fuchsia_inspect::{TreeNameIteratorMarker, TreeNameIteratorProxy, TreeProxy};
186    use fuchsia_async::DurationExt;
187    use fuchsia_inspect::reader::{read_with_timeout, DiagnosticsHierarchy, PartialNodeHierarchy};
188    use std::sync::Arc;
189
190    use futures::FutureExt;
191    use std::time::Duration;
192
193    /// Spawns a `fuchsia.inspect.Tree` server and returns the task handling
194    /// `fuchsia.inspect.Tree` requests and a `TreeProxy` handle to the tree.
195    pub fn spawn_server_proxy(
196        inspector: Inspector,
197        settings: TreeServerSendPreference,
198    ) -> (Arc<fasync::Scope>, TreeProxy) {
199        let scope = Arc::new(fasync::Scope::new());
200        (scope.clone(), spawn_tree_server(inspector, settings, scope.as_handle()).into_proxy())
201    }
202
203    #[fuchsia::test]
204    async fn get_contents() -> Result<(), Error> {
205        let (_server, tree) =
206            spawn_server_proxy(test_inspector(), TreeServerSendPreference::default());
207        let tree_content = tree.get_content().await?;
208        let hierarchy = parse_content(tree_content)?;
209        assert_data_tree!(hierarchy, root: {
210            a: 1i64,
211        });
212        Ok(())
213    }
214
215    #[fuchsia::test]
216    async fn list_child_names() -> Result<(), Error> {
217        let (_server, tree) =
218            spawn_server_proxy(test_inspector(), TreeServerSendPreference::default());
219        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
220        tree.list_child_names(server_end)?;
221        verify_iterator(name_iterator, vec!["lazy-0".to_string()]).await?;
222        Ok(())
223    }
224
225    #[fuchsia::test]
226    async fn open_children() -> Result<(), Error> {
227        let (_server, tree) =
228            spawn_server_proxy(test_inspector(), TreeServerSendPreference::default());
229        let (child_tree, server_end) = fidl::endpoints::create_proxy::<TreeMarker>();
230        tree.open_child("lazy-0", server_end)?;
231        let tree_content = child_tree.get_content().await?;
232        let hierarchy = parse_content(tree_content)?;
233        assert_data_tree!(hierarchy, root: {
234            b: 2u64,
235        });
236        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
237        child_tree.list_child_names(server_end)?;
238        verify_iterator(name_iterator, vec!["lazy-vals-0".to_string()]).await?;
239
240        let (child_tree_2, server_end) = fidl::endpoints::create_proxy::<TreeMarker>();
241        child_tree.open_child("lazy-vals-0", server_end)?;
242        let tree_content = child_tree_2.get_content().await?;
243        let hierarchy = parse_content(tree_content)?;
244        assert_data_tree!(hierarchy, root: {
245            c: 3.0,
246        });
247        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
248        child_tree_2.list_child_names(server_end)?;
249        verify_iterator(name_iterator, vec![]).await?;
250
251        Ok(())
252    }
253
254    #[fuchsia::test]
255    async fn default_snapshots_are_private_on_success() -> Result<(), Error> {
256        let inspector = test_inspector();
257        let (_server, tree_copy) =
258            spawn_server_proxy(inspector.clone(), TreeServerSendPreference::default());
259        let tree_content_copy = tree_copy.get_content().await?;
260
261        inspector.root().record_int("new", 6);
262
263        // A tree that copies the vmo doesn't see the new int
264        let hierarchy = parse_content(tree_content_copy)?;
265        assert_data_tree!(hierarchy, root: {
266            a: 1i64,
267        });
268        Ok(())
269    }
270
271    #[fuchsia::test]
272    async fn force_live_snapshot() -> Result<(), Error> {
273        let inspector = test_inspector();
274        let (_server1, tree_cow) =
275            spawn_server_proxy(inspector.clone(), TreeServerSendPreference::default());
276        let (_server2, tree_live) =
277            spawn_server_proxy(inspector.clone(), TreeServerSendPreference::Live);
278        let tree_content_live = tree_live.get_content().await?;
279        let tree_content_cow = tree_cow.get_content().await?;
280
281        inspector.root().record_int("new", 6);
282
283        // A tree that cow's the vmo doesn't see the new int
284        let hierarchy = parse_content(tree_content_cow)?;
285        assert_data_tree!(hierarchy, root: {
286            a: 1i64,
287        });
288
289        // A tree that live-duplicates the vmo sees the new int
290        let hierarchy = parse_content(tree_content_live)?;
291        assert_data_tree!(hierarchy, root: {
292            a: 1i64,
293            new: 6i64,
294        });
295        Ok(())
296    }
297
298    #[fuchsia::test]
299    async fn read_hanging_lazy_node() -> Result<(), Error> {
300        let inspector = Inspector::default();
301        let root = inspector.root();
302        root.record_string("child", "value");
303
304        root.record_lazy_values("lazy-node-always-hangs", || {
305            async move {
306                fuchsia_async::Timer::new(zx::MonotonicDuration::from_minutes(30).after_now())
307                    .await;
308                Ok(Inspector::default())
309            }
310            .boxed()
311        });
312
313        root.record_lazy_values("lazy-node-doesnt-hang", || {
314            async move {
315                let inspector = Inspector::default();
316                inspector.root().record_string("from", "non-hanging");
317                Ok(inspector)
318            }
319            .boxed()
320        });
321
322        root.record_int("int", 3);
323
324        let (_server, proxy) = spawn_server_proxy(inspector, TreeServerSendPreference::default());
325        let result = read_with_timeout(&proxy, Duration::from_secs(5)).await?;
326        assert_json_diff!(result, root: {
327            child: "value",
328            int: 3i64,
329            from: "non-hanging",
330        });
331
332        Ok(())
333    }
334
335    async fn verify_iterator(
336        name_iterator: TreeNameIteratorProxy,
337        values: Vec<String>,
338    ) -> Result<(), Error> {
339        if !values.is_empty() {
340            assert_eq!(name_iterator.get_next().await?, values);
341        }
342        assert!(name_iterator.get_next().await?.is_empty());
343        assert!(name_iterator.get_next().await.is_err());
344        Ok(())
345    }
346
347    fn parse_content(tree_content: TreeContent) -> Result<DiagnosticsHierarchy, Error> {
348        let buffer = tree_content.buffer.unwrap();
349        Ok(PartialNodeHierarchy::try_from(&buffer.vmo)?.into())
350    }
351
352    fn test_inspector() -> Inspector {
353        let inspector = Inspector::default();
354        inspector.root().record_int("a", 1);
355        inspector.root().record_lazy_child("lazy", || {
356            async move {
357                let inspector = Inspector::default();
358                inspector.root().record_uint("b", 2);
359                inspector.root().record_lazy_values("lazy-vals", || {
360                    async move {
361                        let inspector = Inspector::default();
362                        inspector.root().record_double("c", 3.0);
363                        Ok(inspector)
364                    }
365                    .boxed()
366                });
367                Ok(inspector)
368            }
369            .boxed()
370        });
371        inspector
372    }
373}