fs_inspect/
lib.rs

1// Copyright 2022 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//! This module contains the `FsInspect` trait which filesystems can implement in order to expose
6//! Inspect metrics in a standardized hierarchy. Once `FsInspect` has been implemented, a
7//! filesystem can attach itself to a root node via `FsInspectTree::new`.
8//!
9//! A filesystem's inspect tree can be tested via `fs_test` by enabling the `supports_inspect`
10//! option. This will validate that the inspect tree hierarchy is consistent and that basic
11//! information is reported correctly. See `src/storage/fs_test/inspect.cc` for details.
12
13use async_trait::async_trait;
14use fuchsia_inspect::{LazyNode, Node};
15use futures::FutureExt;
16use std::collections::hash_map::HashMap;
17use std::sync::{Arc, Mutex, Weak};
18
19const INFO_NODE_NAME: &'static str = "fs.info";
20const USAGE_NODE_NAME: &'static str = "fs.usage";
21const VOLUMES_NODE_NAME: &'static str = "fs.volumes";
22
23/// Trait that Rust filesystems should implement to expose required Inspect data.
24///
25/// Once implemented, a filesystem can attach the Inspect data to a given root node by calling
26/// `FsInspectTree::new` which will return ownership of the attached nodes/properties.
27#[async_trait]
28pub trait FsInspect {
29    fn get_info_data(&self) -> InfoData;
30    async fn get_usage_data(&self) -> UsageData;
31}
32
33/// Trait that Rust filesystems which are multi-volume should implement for each volume.
34#[async_trait]
35pub trait FsInspectVolume {
36    async fn get_volume_data(&self) -> VolumeData;
37}
38
39/// Maintains ownership of the various inspect nodes/properties. Will be removed from the root node
40/// they were attached to when dropped.
41pub struct FsInspectTree {
42    _info: LazyNode,
43    _usage: LazyNode,
44    _volumes: LazyNode,
45    volumes_tracker: Arc<Mutex<HashMap<String, Weak<dyn FsInspectVolume + Send + Sync + 'static>>>>,
46}
47
48impl FsInspectTree {
49    /// Attaches Inspect nodes following a standard hierarchy, returning ownership of the newly
50    /// created LazyNodes.
51    pub fn new(fs: Weak<dyn FsInspect + Send + Sync + 'static>, root: &Node) -> FsInspectTree {
52        let fs_clone = fs.clone();
53        let info_node = root.create_lazy_child(INFO_NODE_NAME, move || {
54            let fs_clone = fs_clone.clone();
55            async move {
56                let inspector = fuchsia_inspect::Inspector::default();
57                if let Some(fs) = fs_clone.upgrade() {
58                    fs.get_info_data().record_into(inspector.root());
59                }
60                Ok(inspector)
61            }
62            .boxed()
63        });
64
65        let fs_clone = fs.clone();
66        let usage_node = root.create_lazy_child(USAGE_NODE_NAME, move || {
67            let fs_clone = fs_clone.clone();
68            async move {
69                let inspector = fuchsia_inspect::Inspector::default();
70                if let Some(fs) = fs_clone.upgrade() {
71                    fs.get_usage_data().await.record_into(inspector.root());
72                }
73                Ok(inspector)
74            }
75            .boxed()
76        });
77
78        let volumes_tracker = Arc::new(Mutex::new(HashMap::<
79            String,
80            Weak<dyn FsInspectVolume + Send + Sync + 'static>,
81        >::new()));
82        let tracker_weak = Arc::downgrade(&volumes_tracker);
83        let volumes_node = root.create_lazy_child(VOLUMES_NODE_NAME, move || {
84            let tracker_ref = tracker_weak.clone();
85            async move {
86                let inspector = fuchsia_inspect::Inspector::default();
87                let root = inspector.root();
88                let tracker = match tracker_ref.upgrade() {
89                    Some(tracker) => tracker,
90                    // This probably shouldn't happen, but if it does then it would be during a
91                    // shutdown race, so just return empty.
92                    None => return Ok(inspector),
93                };
94                let volumes = {
95                    let tracker = tracker.lock().unwrap();
96                    let mut volumes = Vec::with_capacity(tracker.len());
97                    for (name, volume) in tracker.iter() {
98                        volumes.push((name.clone(), volume.clone()));
99                    }
100                    volumes
101                };
102                for (name, volume_weak) in volumes {
103                    let volume = match volume_weak.upgrade() {
104                        Some(v) => v,
105                        None => continue,
106                    };
107                    let child = root.create_child(name.clone());
108                    volume.get_volume_data().await.record_into(&child);
109                    root.record(child);
110                }
111                Ok(inspector)
112            }
113            .boxed()
114        });
115
116        FsInspectTree {
117            _info: info_node,
118            _usage: usage_node,
119            _volumes: volumes_node,
120            volumes_tracker,
121        }
122    }
123
124    /// Registers a provider for per-volume data.  If `volume` is dropped, the node will remain
125    /// present in the inspect tree but yield no data, until `Self::unregister_volume` is called.
126    pub fn register_volume(
127        self: &Arc<Self>,
128        name: String,
129        volume: Weak<dyn FsInspectVolume + Send + Sync + 'static>,
130    ) {
131        self.volumes_tracker.lock().unwrap().insert(name, volume);
132    }
133
134    pub fn unregister_volume(&self, name: String) {
135        self.volumes_tracker.lock().unwrap().remove(&name);
136    }
137}
138
139/// fs.info Properties. This is also exported for minfs and blobfs.
140pub struct InfoData {
141    pub id: u64,
142    pub fs_type: u64,
143    pub name: String,
144    pub version_major: u64,
145    pub version_minor: u64,
146    pub block_size: u64,
147    pub max_filename_length: u64,
148    pub oldest_version: Option<String>,
149}
150
151impl InfoData {
152    fn record_into(self, node: &Node) {
153        node.record_uint("id", self.id);
154        node.record_uint("type", self.fs_type);
155        node.record_string("name", self.name);
156        node.record_uint("version_major", self.version_major);
157        node.record_uint("version_minor", self.version_minor);
158        node.record_string(
159            "current_version",
160            format!("{}.{}", self.version_major, self.version_minor),
161        );
162        node.record_uint("block_size", self.block_size);
163        node.record_uint("max_filename_length", self.max_filename_length);
164        if self.oldest_version.is_some() {
165            node.record_string("oldest_version", self.oldest_version.as_ref().unwrap());
166        }
167    }
168}
169
170/// fs.usage Properties
171pub struct UsageData {
172    pub total_bytes: u64,
173    pub used_bytes: u64,
174    pub total_nodes: u64,
175    pub used_nodes: u64,
176}
177
178impl UsageData {
179    fn record_into(self, node: &Node) {
180        node.record_uint("total_bytes", self.total_bytes);
181        node.record_uint("used_bytes", self.used_bytes);
182        node.record_uint("total_nodes", self.total_nodes);
183        node.record_uint("used_nodes", self.used_nodes);
184    }
185}
186
187/// fs.volume/{name} roperties
188pub struct VolumeData {
189    pub used_bytes: u64,
190    pub bytes_limit: Option<u64>,
191    pub used_nodes: u64,
192    pub encrypted: bool,
193    /// Nb: Port is useful in diagnosing the source of stalled pager requests.
194    pub port_koid: u64,
195}
196
197impl VolumeData {
198    fn record_into(self, node: &Node) {
199        node.record_uint("used_bytes", self.used_bytes);
200        if let Some(bytes_limit) = self.bytes_limit {
201            node.record_uint("bytes_limit", bytes_limit);
202        }
203        node.record_uint("used_nodes", self.used_nodes);
204        node.record_bool("encrypted", self.encrypted);
205        node.record_uint("port_koid", self.port_koid);
206    }
207}