blob: 3b31f8b87ac0fb815d4a2fc934d6d5c36d8fc0b6 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! This module contains the `FsInspect` trait which filesystems can implement in order to expose
//! Inspect metrics in a standardized hierarchy. Once `FsInspect` has been implemented, a
//! filesystem can attach itself to a root node via `FsInspectTree::new`.
//!
//! A filesystem's inspect tree can be tested via `fs_test` by enabling the `supports_inspect`
//! option. This will validate that the inspect tree hierarchy is consistent and that basic
//! information is reported correctly. See `src/storage/fs_test/inspect.cc` for details.
use async_trait::async_trait;
use fuchsia_inspect::{LazyNode, Node};
use futures::FutureExt;
use std::collections::hash_map::HashMap;
use std::sync::{Arc, Mutex, Weak};
const INFO_NODE_NAME: &'static str = "fs.info";
const USAGE_NODE_NAME: &'static str = "fs.usage";
const VOLUMES_NODE_NAME: &'static str = "fs.volumes";
/// Trait that Rust filesystems should implement to expose required Inspect data.
///
/// Once implemented, a filesystem can attach the Inspect data to a given root node by calling
/// `FsInspectTree::new` which will return ownership of the attached nodes/properties.
#[async_trait]
pub trait FsInspect {
fn get_info_data(&self) -> InfoData;
async fn get_usage_data(&self) -> UsageData;
}
/// Trait that Rust filesystems which are multi-volume should implement for each volume.
#[async_trait]
pub trait FsInspectVolume {
async fn get_volume_data(&self) -> VolumeData;
}
/// Maintains ownership of the various inspect nodes/properties. Will be removed from the root node
/// they were attached to when dropped.
pub struct FsInspectTree {
_info: LazyNode,
_usage: LazyNode,
_volumes: LazyNode,
volumes_tracker: Arc<Mutex<HashMap<String, Weak<dyn FsInspectVolume + Send + Sync + 'static>>>>,
}
impl FsInspectTree {
/// Attaches Inspect nodes following a standard hierarchy, returning ownership of the newly
/// created LazyNodes.
pub fn new(fs: Weak<dyn FsInspect + Send + Sync + 'static>, root: &Node) -> FsInspectTree {
let fs_clone = fs.clone();
let info_node = root.create_lazy_child(INFO_NODE_NAME, move || {
let fs_clone = fs_clone.clone();
async move {
let inspector = fuchsia_inspect::Inspector::default();
if let Some(fs) = fs_clone.upgrade() {
fs.get_info_data().record_into(inspector.root());
}
Ok(inspector)
}
.boxed()
});
let fs_clone = fs.clone();
let usage_node = root.create_lazy_child(USAGE_NODE_NAME, move || {
let fs_clone = fs_clone.clone();
async move {
let inspector = fuchsia_inspect::Inspector::default();
if let Some(fs) = fs_clone.upgrade() {
fs.get_usage_data().await.record_into(inspector.root());
}
Ok(inspector)
}
.boxed()
});
let volumes_tracker = Arc::new(Mutex::new(HashMap::<
String,
Weak<dyn FsInspectVolume + Send + Sync + 'static>,
>::new()));
let tracker_weak = Arc::downgrade(&volumes_tracker);
let volumes_node = root.create_lazy_child(VOLUMES_NODE_NAME, move || {
let tracker_ref = tracker_weak.clone();
async move {
let inspector = fuchsia_inspect::Inspector::default();
let root = inspector.root();
let tracker = match tracker_ref.upgrade() {
Some(tracker) => tracker,
// This probably shouldn't happen, but if it does then it would be during a
// shutdown race, so just return empty.
None => return Ok(inspector),
};
let volumes = {
let tracker = tracker.lock().unwrap();
let mut volumes = Vec::with_capacity(tracker.len());
for (name, volume) in tracker.iter() {
volumes.push((name.clone(), volume.clone()));
}
volumes
};
for (name, volume_weak) in volumes {
let volume = match volume_weak.upgrade() {
Some(v) => v,
None => continue,
};
let child = root.create_child(name.clone());
volume.get_volume_data().await.record_into(&child);
root.record(child);
}
Ok(inspector)
}
.boxed()
});
FsInspectTree {
_info: info_node,
_usage: usage_node,
_volumes: volumes_node,
volumes_tracker,
}
}
/// Registers a provider for per-volume data. If `volume` is dropped, the node will remain
/// present in the inspect tree but yield no data, until `Self::unregister_volume` is called.
pub fn register_volume(
self: &Arc<Self>,
name: String,
volume: Weak<dyn FsInspectVolume + Send + Sync + 'static>,
) {
self.volumes_tracker.lock().unwrap().insert(name, volume);
}
pub fn unregister_volume(&self, name: String) {
self.volumes_tracker.lock().unwrap().remove(&name);
}
}
/// fs.info Properties
pub struct InfoData {
pub id: u64,
pub fs_type: u64,
pub name: String,
pub version_major: u64,
pub version_minor: u64,
pub block_size: u64,
pub max_filename_length: u64,
pub oldest_version: Option<String>,
}
impl InfoData {
fn record_into(self, node: &Node) {
node.record_uint("id", self.id);
node.record_uint("type", self.fs_type);
node.record_string("name", self.name);
node.record_uint("version_major", self.version_major);
node.record_uint("version_minor", self.version_minor);
node.record_string(
"current_version",
format!("{}.{}", self.version_major, self.version_minor),
);
node.record_uint("block_size", self.block_size);
node.record_uint("max_filename_length", self.max_filename_length);
if self.oldest_version.is_some() {
node.record_string("oldest_version", self.oldest_version.as_ref().unwrap());
}
}
}
/// fs.usage Properties
pub struct UsageData {
pub total_bytes: u64,
pub used_bytes: u64,
pub total_nodes: u64,
pub used_nodes: u64,
}
impl UsageData {
fn record_into(self, node: &Node) {
node.record_uint("total_bytes", self.total_bytes);
node.record_uint("used_bytes", self.used_bytes);
node.record_uint("total_nodes", self.total_nodes);
node.record_uint("used_nodes", self.used_nodes);
}
}
/// fs.volume/{name} roperties
pub struct VolumeData {
pub used_bytes: u64,
pub bytes_limit: Option<u64>,
pub used_nodes: u64,
pub encrypted: bool,
}
impl VolumeData {
fn record_into(self, node: &Node) {
node.record_uint("used_bytes", self.used_bytes);
if let Some(bytes_limit) = self.bytes_limit {
node.record_uint("bytes_limit", bytes_limit);
}
node.record_uint("used_nodes", self.used_nodes);
node.record_bool("encrypted", self.encrypted);
}
}