fuchsia_storage_benchmarks/filesystems/
mod.rs

1// Copyright 2023 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
5use async_trait::async_trait;
6use delivery_blob::{CompressionMode, Type1Blob};
7use fidl::endpoints::ClientEnd;
8use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions};
9use fidl_fuchsia_fxfs::CryptMarker;
10use fidl_fuchsia_io as fio;
11use fs_management::filesystem::{ServingMultiVolumeFilesystem, ServingSingleVolumeFilesystem};
12use fs_management::FSConfig;
13use fuchsia_merkle::Hash;
14use std::path::Path;
15use std::sync::Arc;
16use storage_benchmarks::block_device::BlockDevice;
17use storage_benchmarks::{CacheClearableFilesystem, Filesystem};
18
19mod blobfs;
20mod f2fs;
21mod fxblob;
22pub mod fxfs;
23mod memfs;
24mod minfs;
25mod pkgdir;
26#[cfg(test)]
27mod testing;
28
29pub use blobfs::Blobfs;
30pub use f2fs::F2fs;
31pub use fxblob::Fxblob;
32pub use fxfs::Fxfs;
33pub use memfs::Memfs;
34pub use minfs::Minfs;
35pub use pkgdir::{PkgDirInstance, PkgDirTest};
36
37const MOUNT_PATH: &str = "/benchmark";
38
39/// Struct for holding the name of a blob and its contents in the delivery blob format.
40pub struct DeliveryBlob {
41    pub data: Vec<u8>,
42    pub name: Hash,
43}
44
45impl DeliveryBlob {
46    pub fn new(data: Vec<u8>, mode: CompressionMode) -> Self {
47        let name = fuchsia_merkle::from_slice(&data).root();
48        Self { data: Type1Blob::generate(&data, mode), name }
49    }
50}
51
52/// A trait for filesystems that support reading and writing blobs.
53#[async_trait]
54pub trait BlobFilesystem: CacheClearableFilesystem {
55    /// Writes a blob to the filesystem.
56    ///
57    /// Blobfs and Fxblob write blobs using different protocols. How a blob is written is
58    /// implemented in the filesystem so benchmarks don't have to know which protocol to use.
59    async fn write_blob(&self, blob: &DeliveryBlob);
60
61    /// Blobfs and Fxblob open and read blobs using different protocols. Benchmarks should remain
62    /// agnostic to which protocol is being used.
63    async fn get_vmo(&self, blob: &DeliveryBlob) -> zx::Vmo;
64
65    /// Returns the exposed dir of Blobfs or Fxblobs' blob volume.
66    fn exposed_dir(&self) -> &fio::DirectoryProxy;
67}
68
69enum FsType {
70    SingleVolume(ServingSingleVolumeFilesystem),
71    MultiVolume(ServingMultiVolumeFilesystem),
72}
73
74pub type CryptClientFn = Arc<dyn Fn() -> ClientEnd<CryptMarker> + Send + Sync>;
75
76pub struct FsManagementFilesystemInstance {
77    fs: fs_management::filesystem::Filesystem,
78    crypt_client_fn: Option<CryptClientFn>,
79    serving_filesystem: Option<FsType>,
80    as_blob: bool,
81    // Keep the underlying block device alive for as long as we are using the filesystem.
82    _block_device: Box<dyn BlockDevice>,
83}
84
85impl FsManagementFilesystemInstance {
86    pub async fn new<FSC: FSConfig>(
87        config: FSC,
88        block_device: Box<dyn BlockDevice>,
89        crypt_client_fn: Option<CryptClientFn>,
90        as_blob: bool,
91    ) -> Self {
92        let mut fs = fs_management::filesystem::Filesystem::from_boxed_config(
93            block_device.connector(),
94            Box::new(config),
95        );
96        fs.format().await.expect("Failed to format the filesystem");
97        let serving_filesystem = if fs.config().is_multi_volume() {
98            let mut serving_filesystem =
99                fs.serve_multi_volume().await.expect("Failed to start the filesystem");
100            let vol = serving_filesystem
101                .create_volume(
102                    "default",
103                    CreateOptions::default(),
104                    MountOptions {
105                        crypt: crypt_client_fn.as_ref().map(|f| f()),
106                        as_blob: Some(as_blob),
107                        ..MountOptions::default()
108                    },
109                )
110                .await
111                .expect("Failed to create volume");
112            vol.bind_to_path(MOUNT_PATH).expect("Failed to bind the volume");
113            FsType::MultiVolume(serving_filesystem)
114        } else {
115            let mut serving_filesystem = fs.serve().await.expect("Failed to start the filesystem");
116            serving_filesystem.bind_to_path(MOUNT_PATH).expect("Failed to bind the filesystem");
117            FsType::SingleVolume(serving_filesystem)
118        };
119        Self {
120            fs,
121            crypt_client_fn,
122            serving_filesystem: Some(serving_filesystem),
123            _block_device: block_device,
124            as_blob,
125        }
126    }
127
128    fn exposed_dir(&self) -> &fio::DirectoryProxy {
129        let fs = self.serving_filesystem.as_ref().unwrap();
130        match fs {
131            FsType::SingleVolume(serving_filesystem) => serving_filesystem.exposed_dir(),
132            FsType::MultiVolume(serving_filesystem) => {
133                serving_filesystem.volume("default").unwrap().exposed_dir()
134            }
135        }
136    }
137
138    // Instead of returning the directory that is the data root for this filesystem, return the root
139    // that would be used for accessing top level service protocols.
140    fn exposed_services_dir(&self) -> &fio::DirectoryProxy {
141        let fs = self.serving_filesystem.as_ref().unwrap();
142        match fs {
143            FsType::SingleVolume(serving_filesystem) => serving_filesystem.exposed_dir(),
144            FsType::MultiVolume(serving_filesystem) => serving_filesystem.exposed_dir(),
145        }
146    }
147}
148
149#[async_trait]
150impl Filesystem for FsManagementFilesystemInstance {
151    async fn shutdown(mut self) {
152        if let Some(fs) = self.serving_filesystem.take() {
153            match fs {
154                FsType::SingleVolume(fs) => fs.shutdown().await.expect("Failed to stop filesystem"),
155                FsType::MultiVolume(fs) => fs.shutdown().await.expect("Failed to stop filesystem"),
156            }
157        }
158    }
159
160    fn benchmark_dir(&self) -> &Path {
161        Path::new(MOUNT_PATH)
162    }
163}
164
165#[async_trait]
166impl CacheClearableFilesystem for FsManagementFilesystemInstance {
167    async fn clear_cache(&mut self) {
168        // Remount the filesystem to guarantee that all cached data from reads and write is cleared.
169        let serving_filesystem = self.serving_filesystem.take().unwrap();
170        let serving_filesystem = match serving_filesystem {
171            FsType::SingleVolume(serving_filesystem) => {
172                serving_filesystem.shutdown().await.expect("Failed to stop the filesystem");
173                let mut serving_filesystem =
174                    self.fs.serve().await.expect("Failed to start the filesystem");
175                serving_filesystem.bind_to_path(MOUNT_PATH).expect("Failed to bind the filesystem");
176                FsType::SingleVolume(serving_filesystem)
177            }
178            FsType::MultiVolume(serving_filesystem) => {
179                serving_filesystem.shutdown().await.expect("Failed to stop the filesystem");
180                let mut serving_filesystem =
181                    self.fs.serve_multi_volume().await.expect("Failed to start the filesystem");
182                let vol = serving_filesystem
183                    .open_volume(
184                        "default",
185                        MountOptions {
186                            crypt: self.crypt_client_fn.as_ref().map(|f| f()),
187                            as_blob: Some(self.as_blob),
188                            ..MountOptions::default()
189                        },
190                    )
191                    .await
192                    .expect("Failed to create volume");
193                vol.bind_to_path(MOUNT_PATH).expect("Failed to bind the volume");
194                FsType::MultiVolume(serving_filesystem)
195            }
196        };
197        self.serving_filesystem = Some(serving_filesystem);
198    }
199}