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::task::Poll;
202    use std::time::Duration;
203
204    /// Spawns a `fuchsia.inspect.Tree` server and returns the task handling
205    /// `fuchsia.inspect.Tree` requests and a `TreeProxy` handle to the tree.
206    pub fn spawn_server_proxy(
207        inspector: Inspector,
208        settings: TreeServerSendPreference,
209    ) -> (Arc<fasync::Scope>, TreeProxy) {
210        let scope = Arc::new(fasync::Scope::new());
211        (scope.clone(), spawn_tree_server(inspector, settings, scope.as_handle()).into_proxy())
212    }
213
214    #[fuchsia::test]
215    async fn get_contents() -> Result<(), Error> {
216        let (_server, tree) =
217            spawn_server_proxy(test_inspector(), TreeServerSendPreference::default());
218        let tree_content = tree.get_content().await?;
219        let hierarchy = parse_content(tree_content)?;
220        assert_data_tree!(hierarchy, root: {
221            a: 1i64,
222        });
223        Ok(())
224    }
225
226    #[fuchsia::test]
227    async fn list_child_names() -> Result<(), Error> {
228        let (_server, tree) =
229            spawn_server_proxy(test_inspector(), TreeServerSendPreference::default());
230        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
231        tree.list_child_names(server_end)?;
232        verify_iterator(name_iterator, vec!["lazy-0".to_string()]).await?;
233        Ok(())
234    }
235
236    #[fuchsia::test]
237    async fn oversized_tree_name_is_dropped() -> Result<(), Error> {
238        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
239        let scope = fasync::Scope::new();
240        scope.spawn(async move {
241            let oversized = "a".repeat(ZX_CHANNEL_MAX_MSG_BYTES as usize);
242            run_tree_name_iterator_server(
243                vec![oversized, "normal".to_string()],
244                server_end.into_stream(),
245            )
246            .await
247            .unwrap();
248        });
249        verify_iterator(name_iterator, vec!["normal".to_string()]).await?;
250        Ok(())
251    }
252
253    #[fuchsia::test]
254    async fn open_children() -> Result<(), Error> {
255        let (_server, tree) =
256            spawn_server_proxy(test_inspector(), TreeServerSendPreference::default());
257        let (child_tree, server_end) = fidl::endpoints::create_proxy::<TreeMarker>();
258        tree.open_child("lazy-0", server_end)?;
259        let tree_content = child_tree.get_content().await?;
260        let hierarchy = parse_content(tree_content)?;
261        assert_data_tree!(hierarchy, root: {
262            b: 2u64,
263        });
264        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
265        child_tree.list_child_names(server_end)?;
266        verify_iterator(name_iterator, vec!["lazy-vals-0".to_string()]).await?;
267
268        let (child_tree_2, server_end) = fidl::endpoints::create_proxy::<TreeMarker>();
269        child_tree.open_child("lazy-vals-0", server_end)?;
270        let tree_content = child_tree_2.get_content().await?;
271        let hierarchy = parse_content(tree_content)?;
272        assert_data_tree!(hierarchy, root: {
273            c: 3.0,
274        });
275        let (name_iterator, server_end) = fidl::endpoints::create_proxy::<TreeNameIteratorMarker>();
276        child_tree_2.list_child_names(server_end)?;
277        verify_iterator(name_iterator, vec![]).await?;
278
279        Ok(())
280    }
281
282    #[fuchsia::test]
283    async fn default_snapshots_are_private_on_success() -> Result<(), Error> {
284        let inspector = test_inspector();
285        let (_server, tree_copy) =
286            spawn_server_proxy(inspector.clone(), TreeServerSendPreference::default());
287        let tree_content_copy = tree_copy.get_content().await?;
288
289        inspector.root().record_int("new", 6);
290
291        // A tree that copies the vmo doesn't see the new int
292        let hierarchy = parse_content(tree_content_copy)?;
293        assert_data_tree!(hierarchy, root: {
294            a: 1i64,
295        });
296        Ok(())
297    }
298
299    #[fuchsia::test]
300    async fn force_live_snapshot() -> Result<(), Error> {
301        let inspector = test_inspector();
302        let (_server1, tree_cow) =
303            spawn_server_proxy(inspector.clone(), TreeServerSendPreference::default());
304        let (_server2, tree_live) =
305            spawn_server_proxy(inspector.clone(), TreeServerSendPreference::Live);
306        let tree_content_live = tree_live.get_content().await?;
307        let tree_content_cow = tree_cow.get_content().await?;
308
309        inspector.root().record_int("new", 6);
310
311        // A tree that cow's the vmo doesn't see the new int
312        let hierarchy = parse_content(tree_content_cow)?;
313        assert_data_tree!(hierarchy, root: {
314            a: 1i64,
315        });
316
317        // A tree that live-duplicates the vmo sees the new int
318        let hierarchy = parse_content(tree_content_live)?;
319        assert_data_tree!(hierarchy, root: {
320            a: 1i64,
321            new: 6i64,
322        });
323        Ok(())
324    }
325
326    #[fuchsia::test]
327    async fn read_hanging_lazy_node() -> Result<(), Error> {
328        let inspector = Inspector::default();
329        let root = inspector.root();
330        root.record_string("child", "value");
331
332        root.record_lazy_values("lazy-node-always-hangs", || {
333            async move {
334                fuchsia_async::Timer::new(zx::MonotonicDuration::from_minutes(30).after_now())
335                    .await;
336                Ok(Inspector::default())
337            }
338            .boxed()
339        });
340
341        root.record_lazy_values("lazy-node-doesnt-hang", || {
342            async move {
343                let inspector = Inspector::default();
344                inspector.root().record_string("from", "non-hanging");
345                Ok(inspector)
346            }
347            .boxed()
348        });
349
350        root.record_int("int", 3);
351
352        let instrumentation = Inspector::default();
353        let counter = instrumentation.root().create_uint("counter", 0);
354
355        let (_server, proxy) = spawn_server_proxy(inspector, TreeServerSendPreference::default());
356        let result = read_with_timeout(&proxy, Duration::from_secs(5), &counter).await?;
357
358        assert_json_diff!(instrumentation, root: { counter: 1 });
359
360        assert_json_diff!(result, root: {
361            child: "value",
362            int: 3i64,
363            from: "non-hanging",
364        });
365
366        Ok(())
367    }
368
369    #[fuchsia::test(allow_stalls = false)]
370    async fn multiple_hanging_lazy_nodes_time_out_concurrently() {
371        let inspector = Inspector::default();
372        let root = inspector.root();
373        root.record_int("int", 3);
374
375        for i in 0..3 {
376            root.record_lazy_values(format!("lazy-node-always-hangs-{i}"), || {
377                async move {
378                    futures::future::pending::<()>().await;
379                    Ok(Inspector::default())
380                }
381                .boxed()
382            });
383        }
384
385        root.record_lazy_values("lazy-node-doesnt-hang", || {
386            async move {
387                let inspector = Inspector::default();
388                inspector.root().record_string("from", "non-hanging");
389                Ok(inspector)
390            }
391            .boxed()
392        });
393
394        root.record_lazy_child("healthy", || {
395            async move {
396                let inspector = Inspector::default();
397                inspector.root().record_string("status", "ok");
398                inspector.root().record_lazy_values("nested-lazy-node-always-hangs", || {
399                    async move {
400                        futures::future::pending::<()>().await;
401                        Ok(Inspector::default())
402                    }
403                    .boxed()
404                });
405                Ok(inspector)
406            }
407            .boxed()
408        });
409
410        let instrumentation = Inspector::default();
411        let counter = instrumentation.root().create_uint("counter", 0);
412
413        let (_server, proxy) = spawn_server_proxy(inspector, TreeServerSendPreference::default());
414        let mut read = Box::pin(read_with_timeout(&proxy, Duration::from_secs(5), &counter));
415
416        // Without fake time advancing, the hanging lazy nodes keep the read pending.
417        assert!(fasync::TestExecutor::poll_until_stalled(&mut read).await.is_pending());
418        assert_json_diff!(instrumentation, root: { counter: 0 });
419
420        // A single timeout period is enough for every hanging lazy node to time out, including
421        // the nested one, since they are all read concurrently.
422        fasync::TestExecutor::advance_to(fasync::MonotonicInstant::after(
423            zx::MonotonicDuration::from_seconds(5),
424        ))
425        .await;
426        let result = match fasync::TestExecutor::poll_until_stalled(&mut read).await {
427            Poll::Ready(result) => result.expect("read hierarchy"),
428            Poll::Pending => panic!("read should complete after a single timeout period"),
429        };
430
431        assert_json_diff!(instrumentation, root: { counter: 4 });
432        assert_json_diff!(result, root: {
433            int: 3i64,
434            from: "non-hanging",
435            healthy: {
436                status: "ok",
437            },
438        });
439    }
440
441    async fn verify_iterator(
442        name_iterator: TreeNameIteratorProxy,
443        values: Vec<String>,
444    ) -> Result<(), Error> {
445        if !values.is_empty() {
446            assert_eq!(name_iterator.get_next().await?, values);
447        }
448        assert!(name_iterator.get_next().await?.is_empty());
449        assert!(name_iterator.get_next().await.is_err());
450        Ok(())
451    }
452
453    fn parse_content(tree_content: TreeContent) -> Result<DiagnosticsHierarchy, Error> {
454        let buffer = tree_content.buffer.unwrap();
455        Ok(PartialNodeHierarchy::try_from(&buffer.vmo)?.into())
456    }
457
458    fn test_inspector() -> Inspector {
459        let inspector = Inspector::default();
460        inspector.root().record_int("a", 1);
461        inspector.root().record_lazy_child("lazy", || {
462            async move {
463                let inspector = Inspector::default();
464                inspector.root().record_uint("b", 2);
465                inspector.root().record_lazy_values("lazy-vals", || {
466                    async move {
467                        let inspector = Inspector::default();
468                        inspector.root().record_double("c", 3.0);
469                        Ok(inspector)
470                    }
471                    .boxed()
472                });
473                Ok(inspector)
474            }
475            .boxed()
476        });
477        inspector
478    }
479}