Skip to main content

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