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                            let next_bytes = bytes_used
162                                + 16 // String overhead
163                                + fidl::encoding::round_up_to_align(value.len(), 8);
164                            if next_bytes > ZX_CHANNEL_MAX_MSG_BYTES as usize {
165                                if result.is_empty() {
166                                    warn!("Dropping tree name that exceeds channel message size");
167                                    values_iter.next();
168                                    continue;
169                                }
170                                break;
171                            }
172                            bytes_used = next_bytes;
173                            result.push(values_iter.next().unwrap());
174                        }
175                    }
176                }
177                if result.is_empty() {
178                    responder.send(&[])?;
179                    return Ok(());
180                }
181                responder.send(&result)?;
182            }
183            TreeNameIteratorRequest::_UnknownMethod { ordinal, method_type, .. } => {
184                warn!(ordinal, method_type:?; "Unknown request");
185            }
186        }
187    }
188    Ok(())
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use diagnostics_assertions::{assert_data_tree, assert_json_diff};
195    use fidl_fuchsia_inspect::{TreeNameIteratorMarker, TreeNameIteratorProxy, TreeProxy};
196    use fuchsia_async::DurationExt;
197    use fuchsia_inspect::reader::{DiagnosticsHierarchy, PartialNodeHierarchy, read_with_timeout};
198    use std::sync::Arc;
199
200    use futures::FutureExt;
201    use std::time::Duration;
202
203    /// Spawns a `fuchsia.inspect.Tree` server and returns the task handling
204    /// `fuchsia.inspect.Tree` requests and a `TreeProxy` handle to the tree.
205    pub fn spawn_server_proxy(
206        inspector: Inspector,
207        settings: TreeServerSendPreference,
208    ) -> (Arc<fasync::Scope>, TreeProxy) {
209        let scope = Arc::new(fasync::Scope::new());
210        (scope.clone(), spawn_tree_server(inspector, settings, scope.as_handle()).into_proxy())
211    }
212
213    #[fuchsia::test]
214    async fn get_contents() -> Result<(), Error> {
215        let (_server, tree) =
216            spawn_server_proxy(test_inspector(), TreeServerSendPreference::default());
217        let tree_content = tree.get_content().await?;
218        let hierarchy = parse_content(tree_content)?;
219        assert_data_tree!(hierarchy, root: {
220            a: 1i64,
221        });
222        Ok(())
223    }
224
225    #[fuchsia::test]
226    async fn list_child_names() -> Result<(), Error> {
227        let (_server, tree) =
228            spawn_server_proxy(test_inspector(), TreeServerSendPreference::default());
229        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
230        tree.list_child_names(server_end)?;
231        verify_iterator(name_iterator, vec!["lazy-0".to_string()]).await?;
232        Ok(())
233    }
234
235    #[fuchsia::test]
236    async fn oversized_tree_name_is_dropped() -> Result<(), Error> {
237        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
238        let scope = fasync::Scope::new();
239        scope.spawn(async move {
240            let oversized = "a".repeat(ZX_CHANNEL_MAX_MSG_BYTES as usize);
241            run_tree_name_iterator_server(
242                vec![oversized, "normal".to_string()],
243                server_end.into_stream(),
244            )
245            .await
246            .unwrap();
247        });
248        verify_iterator(name_iterator, vec!["normal".to_string()]).await?;
249        Ok(())
250    }
251
252    #[fuchsia::test]
253    async fn open_children() -> Result<(), Error> {
254        let (_server, tree) =
255            spawn_server_proxy(test_inspector(), TreeServerSendPreference::default());
256        let (child_tree, server_end) = fidl::endpoints::create_proxy::<TreeMarker>();
257        tree.open_child("lazy-0", server_end)?;
258        let tree_content = child_tree.get_content().await?;
259        let hierarchy = parse_content(tree_content)?;
260        assert_data_tree!(hierarchy, root: {
261            b: 2u64,
262        });
263        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
264        child_tree.list_child_names(server_end)?;
265        verify_iterator(name_iterator, vec!["lazy-vals-0".to_string()]).await?;
266
267        let (child_tree_2, server_end) = fidl::endpoints::create_proxy::<TreeMarker>();
268        child_tree.open_child("lazy-vals-0", server_end)?;
269        let tree_content = child_tree_2.get_content().await?;
270        let hierarchy = parse_content(tree_content)?;
271        assert_data_tree!(hierarchy, root: {
272            c: 3.0,
273        });
274        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
275        child_tree_2.list_child_names(server_end)?;
276        verify_iterator(name_iterator, vec![]).await?;
277
278        Ok(())
279    }
280
281    #[fuchsia::test]
282    async fn default_snapshots_are_private_on_success() -> Result<(), Error> {
283        let inspector = test_inspector();
284        let (_server, tree_copy) =
285            spawn_server_proxy(inspector.clone(), TreeServerSendPreference::default());
286        let tree_content_copy = tree_copy.get_content().await?;
287
288        inspector.root().record_int("new", 6);
289
290        // A tree that copies the vmo doesn't see the new int
291        let hierarchy = parse_content(tree_content_copy)?;
292        assert_data_tree!(hierarchy, root: {
293            a: 1i64,
294        });
295        Ok(())
296    }
297
298    #[fuchsia::test]
299    async fn force_live_snapshot() -> Result<(), Error> {
300        let inspector = test_inspector();
301        let (_server1, tree_cow) =
302            spawn_server_proxy(inspector.clone(), TreeServerSendPreference::default());
303        let (_server2, tree_live) =
304            spawn_server_proxy(inspector.clone(), TreeServerSendPreference::Live);
305        let tree_content_live = tree_live.get_content().await?;
306        let tree_content_cow = tree_cow.get_content().await?;
307
308        inspector.root().record_int("new", 6);
309
310        // A tree that cow's the vmo doesn't see the new int
311        let hierarchy = parse_content(tree_content_cow)?;
312        assert_data_tree!(hierarchy, root: {
313            a: 1i64,
314        });
315
316        // A tree that live-duplicates the vmo sees the new int
317        let hierarchy = parse_content(tree_content_live)?;
318        assert_data_tree!(hierarchy, root: {
319            a: 1i64,
320            new: 6i64,
321        });
322        Ok(())
323    }
324
325    #[fuchsia::test]
326    async fn read_hanging_lazy_node() -> Result<(), Error> {
327        let inspector = Inspector::default();
328        let root = inspector.root();
329        root.record_string("child", "value");
330
331        root.record_lazy_values("lazy-node-always-hangs", || {
332            async move {
333                fuchsia_async::Timer::new(zx::MonotonicDuration::from_minutes(30).after_now())
334                    .await;
335                Ok(Inspector::default())
336            }
337            .boxed()
338        });
339
340        root.record_lazy_values("lazy-node-doesnt-hang", || {
341            async move {
342                let inspector = Inspector::default();
343                inspector.root().record_string("from", "non-hanging");
344                Ok(inspector)
345            }
346            .boxed()
347        });
348
349        root.record_int("int", 3);
350
351        let instrumentation = Inspector::default();
352        let counter = instrumentation.root().create_uint("counter", 0);
353
354        let (_server, proxy) = spawn_server_proxy(inspector, TreeServerSendPreference::default());
355        let result = read_with_timeout(&proxy, Duration::from_secs(5), &counter).await?;
356
357        assert_json_diff!(instrumentation, root: { counter: 1 });
358
359        assert_json_diff!(result, root: {
360            child: "value",
361            int: 3i64,
362            from: "non-hanging",
363        });
364
365        Ok(())
366    }
367
368    async fn verify_iterator(
369        name_iterator: TreeNameIteratorProxy,
370        values: Vec<String>,
371    ) -> Result<(), Error> {
372        if !values.is_empty() {
373            assert_eq!(name_iterator.get_next().await?, values);
374        }
375        assert!(name_iterator.get_next().await?.is_empty());
376        assert!(name_iterator.get_next().await.is_err());
377        Ok(())
378    }
379
380    fn parse_content(tree_content: TreeContent) -> Result<DiagnosticsHierarchy, Error> {
381        let buffer = tree_content.buffer.unwrap();
382        Ok(PartialNodeHierarchy::try_from(&buffer.vmo)?.into())
383    }
384
385    fn test_inspector() -> Inspector {
386        let inspector = Inspector::default();
387        inspector.root().record_int("a", 1);
388        inspector.root().record_lazy_child("lazy", || {
389            async move {
390                let inspector = Inspector::default();
391                inspector.root().record_uint("b", 2);
392                inspector.root().record_lazy_values("lazy-vals", || {
393                    async move {
394                        let inspector = Inspector::default();
395                        inspector.root().record_double("c", 3.0);
396                        Ok(inspector)
397                    }
398                    .boxed()
399                });
400                Ok(inspector)
401            }
402            .boxed()
403        });
404        inspector
405    }
406}