Skip to main content

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