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