Skip to main content

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 blob_writer::BlobWriter;
7use delivery_blob::{CompressionMode, DeliveryBlobType, Type1Blob, Type3Blob};
8use fidl::endpoints::ClientEnd;
9use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions};
10use fidl_fuchsia_fxfs::{BlobCreatorProxy, BlobReaderProxy, CryptMarker};
11use fidl_fuchsia_io as fio;
12use fs_management::FSConfig;
13use fs_management::filesystem::{
14    Filesystem as FsManagementFilesystem, ServingMultiVolumeFilesystem,
15    ServingSingleVolumeFilesystem, ServingVolume,
16};
17use fuchsia_merkle::Hash;
18use std::path::Path;
19use std::sync::Arc;
20use storage_benchmarks::block_device::BlockDevice;
21use storage_benchmarks::{CacheClearableFilesystem, Filesystem};
22
23mod blobfs;
24mod f2fs;
25mod fxblob;
26pub mod fxfs;
27mod memfs;
28mod minfs;
29mod pkgdir;
30#[cfg(test)]
31mod testing;
32
33pub use blobfs::Blobfs;
34pub use f2fs::F2fs;
35pub use fxblob::{Fxblob, FxblobInstance};
36pub use fxfs::Fxfs;
37pub use memfs::Memfs;
38pub use minfs::Minfs;
39pub use pkgdir::{PkgDirInstance, PkgDirTest};
40
41const MOUNT_PATH: &str = "/benchmark";
42
43/// Struct for holding the name of a blob and its contents in the delivery blob format.
44pub struct DeliveryBlob {
45    pub data: Vec<u8>,
46    pub name: Hash,
47}
48
49impl DeliveryBlob {
50    pub fn new(data: Vec<u8>, mode: CompressionMode) -> Self {
51        Self::new_with_type(DeliveryBlobType::Type1, data, mode)
52    }
53
54    pub fn new_with_type(
55        delivery_type: DeliveryBlobType,
56        data: Vec<u8>,
57        mode: CompressionMode,
58    ) -> Self {
59        let name = fuchsia_merkle::root_from_slice(&data);
60        let data = match delivery_type {
61            DeliveryBlobType::Type1 => Type1Blob::generate(&data, mode),
62            DeliveryBlobType::Type3 => Type3Blob::generate(&data, mode),
63            _ => panic!("Unsupported delivery blob type"),
64        };
65        Self { data, name }
66    }
67}
68
69/// A trait for filesystems that support reading and writing blobs.
70#[async_trait]
71pub trait BlobFilesystem: CacheClearableFilesystem {
72    /// Writes a blob to the filesystem.
73    async fn write_blob(&self, blob: &DeliveryBlob) {
74        let writer_client_end = self
75            .blob_creator()
76            .create(&blob.name.into(), false)
77            .await
78            .expect("transport error on BlobCreator.Create")
79            .expect("failed to create blob");
80        let writer = writer_client_end.into_proxy();
81        let mut blob_writer = BlobWriter::create(writer, blob.data.len() as u64)
82            .await
83            .expect("failed to create BlobWriter");
84        blob_writer.write(&blob.data).await.unwrap();
85    }
86
87    /// Gets a VMO backing a blob.
88    async fn get_vmo(&self, name: &Hash) -> zx::Vmo {
89        self.blob_reader()
90            .get_vmo(&*name)
91            .await
92            .expect("transport error on BlobReader.GetVmo")
93            .expect("failed to get vmo")
94    }
95
96    async fn remove_blob(&self, name: &Hash) {
97        let root = fuchsia_fs::directory::open_directory(
98            self.exposed_dir(),
99            "root",
100            fio::PERM_READABLE | fio::PERM_WRITABLE,
101        )
102        .await
103        .expect("failed to open blob directory");
104        root.unlink(&name.to_string(), &fio::UnlinkOptions::default())
105            .await
106            .expect("transport error on Directory.Unlink")
107            .expect("failed to unlink blob");
108        root.sync()
109            .await
110            .expect("transport error on Directory.Sync")
111            .expect("failed to sync blob directory");
112    }
113
114    /// Direct access to the BlobCreator protocol.
115    fn blob_creator(&self) -> &BlobCreatorProxy;
116
117    /// Direct access to the BlobReader protocol.
118    fn blob_reader(&self) -> &BlobReaderProxy;
119
120    /// Returns the exposed dir of the blob volume.
121    fn exposed_dir(&self) -> &fio::DirectoryProxy;
122}
123
124enum FsType {
125    SingleVolume(ServingSingleVolumeFilesystem),
126    MultiVolume(ServingMultiVolumeFilesystem, ServingVolume),
127}
128
129pub type CryptClientFn = Arc<dyn Fn() -> ClientEnd<CryptMarker> + Send + Sync>;
130
131pub struct FsManagementFilesystemInstance {
132    config_creator: Box<dyn Fn() -> Box<dyn FSConfig> + Send + Sync>,
133    crypt_client_fn: Option<CryptClientFn>,
134    serving_filesystem: Option<FsType>,
135    as_blob: bool,
136    // Keep the underlying block device alive for as long as we are using the filesystem.
137    block_device: Box<dyn BlockDevice>,
138}
139
140impl FsManagementFilesystemInstance {
141    pub async fn new<FSC: FSConfig>(
142        config_creator: impl (Fn() -> FSC) + Send + Sync + 'static,
143        block_device: Box<dyn BlockDevice>,
144        crypt_client_fn: Option<CryptClientFn>,
145        as_blob: bool,
146    ) -> Self {
147        let config_creator = Box::new(move || Box::new(config_creator()) as Box<dyn FSConfig>);
148        let mut fs =
149            FsManagementFilesystem::from_boxed_config(block_device.connector(), config_creator());
150        fs.format().await.expect("Failed to format the filesystem");
151        let serving_filesystem = if fs.config().is_multi_volume() {
152            let serving_filesystem =
153                fs.serve_multi_volume().await.expect("Failed to start the filesystem");
154            let mut vol = serving_filesystem
155                .create_volume(
156                    "default",
157                    CreateOptions::default(),
158                    MountOptions {
159                        crypt: crypt_client_fn.as_ref().map(|f| f()),
160                        as_blob: Some(as_blob),
161                        ..MountOptions::default()
162                    },
163                )
164                .await
165                .expect("Failed to create volume");
166            vol.bind_to_path(MOUNT_PATH).expect("Failed to bind the volume");
167            FsType::MultiVolume(serving_filesystem, vol)
168        } else {
169            let mut serving_filesystem = fs.serve().await.expect("Failed to start the filesystem");
170            serving_filesystem.bind_to_path(MOUNT_PATH).expect("Failed to bind the filesystem");
171            FsType::SingleVolume(serving_filesystem)
172        };
173        Self {
174            config_creator,
175            crypt_client_fn,
176            serving_filesystem: Some(serving_filesystem),
177            as_blob,
178            block_device,
179        }
180    }
181
182    fn exposed_dir(&self) -> &fio::DirectoryProxy {
183        let fs = self.serving_filesystem.as_ref().unwrap();
184        match fs {
185            FsType::SingleVolume(serving_filesystem) => serving_filesystem.exposed_dir(),
186            FsType::MultiVolume(_, serving_volume) => serving_volume.exposed_dir(),
187        }
188    }
189
190    // Instead of returning the directory that is the data root for this filesystem, return the root
191    // that would be used for accessing top level service protocols.
192    pub(crate) fn exposed_services_dir(&self) -> &fio::DirectoryProxy {
193        let fs = self.serving_filesystem.as_ref().unwrap();
194        match fs {
195            FsType::SingleVolume(serving_filesystem) => serving_filesystem.exposed_dir(),
196            FsType::MultiVolume(serving_filesystem, _) => serving_filesystem.exposed_dir(),
197        }
198    }
199
200    fn fs(&self) -> FsManagementFilesystem {
201        FsManagementFilesystem::from_boxed_config(
202            self.block_device.connector(),
203            (self.config_creator)(),
204        )
205    }
206}
207
208#[async_trait]
209impl Filesystem for FsManagementFilesystemInstance {
210    async fn shutdown(mut self) {
211        if let Some(fs) = self.serving_filesystem.take() {
212            match fs {
213                FsType::SingleVolume(fs) => fs.shutdown().await.expect("Failed to stop filesystem"),
214                FsType::MultiVolume(fs, vol) => {
215                    vol.shutdown().await.expect("Failed to stop volume");
216                    fs.shutdown().await.expect("Failed to stop filesystem")
217                }
218            }
219        }
220    }
221
222    fn benchmark_dir(&self) -> &Path {
223        Path::new(MOUNT_PATH)
224    }
225}
226
227#[async_trait]
228impl CacheClearableFilesystem for FsManagementFilesystemInstance {
229    async fn clear_cache(&mut self) {
230        // Remount the filesystem to guarantee that all cached data from reads and write is cleared.
231        let serving_filesystem = self.serving_filesystem.take().unwrap();
232        let serving_filesystem = match serving_filesystem {
233            FsType::SingleVolume(serving_filesystem) => {
234                serving_filesystem.shutdown().await.expect("Failed to stop the filesystem");
235                let mut serving_filesystem =
236                    self.fs().serve().await.expect("Failed to start the filesystem");
237                serving_filesystem.bind_to_path(MOUNT_PATH).expect("Failed to bind the filesystem");
238                FsType::SingleVolume(serving_filesystem)
239            }
240            FsType::MultiVolume(serving_filesystem, volume) => {
241                volume.shutdown().await.expect("Failed to stop the volume");
242                serving_filesystem.shutdown().await.expect("Failed to stop the filesystem");
243                let serving_filesystem =
244                    self.fs().serve_multi_volume().await.expect("Failed to start the filesystem");
245                let mut vol = serving_filesystem
246                    .open_volume(
247                        "default",
248                        MountOptions {
249                            crypt: self.crypt_client_fn.as_ref().map(|f| f()),
250                            as_blob: Some(self.as_blob),
251                            ..MountOptions::default()
252                        },
253                    )
254                    .await
255                    .expect("Failed to create volume");
256                vol.bind_to_path(MOUNT_PATH).expect("Failed to bind the volume");
257                FsType::MultiVolume(serving_filesystem, vol)
258            }
259        };
260        self.serving_filesystem = Some(serving_filesystem);
261    }
262}