blob: 12e5d620571cfdefdff8b5e09275ccae7877c6be [file] [log] [blame]
// Copyright 2023 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.
use {
async_trait::async_trait,
delivery_blob::{CompressionMode, Type1Blob},
fidl_fuchsia_fxfs::MountOptions,
fidl_fuchsia_io as fio,
fs_management::{
filesystem::{ServingMultiVolumeFilesystem, ServingSingleVolumeFilesystem},
FSConfig,
},
fuchsia_merkle::Hash,
fuchsia_zircon as zx,
std::path::Path,
storage_benchmarks::{block_device::BlockDevice, CacheClearableFilesystem, Filesystem},
};
mod blobfs;
mod f2fs;
mod fxblob;
pub mod fxfs;
mod memfs;
mod minfs;
mod pkgdir;
#[cfg(test)]
mod testing;
pub use {
blobfs::Blobfs,
f2fs::F2fs,
fxblob::Fxblob,
fxfs::Fxfs,
memfs::Memfs,
minfs::Minfs,
pkgdir::{PkgDirInstance, PkgDirTest},
};
const MOUNT_PATH: &str = "/benchmark";
/// Struct for holding the name of a blob and its contents in the delivery blob format.
pub struct DeliveryBlob {
pub data: Vec<u8>,
pub name: Hash,
}
impl DeliveryBlob {
pub fn new(data: Vec<u8>, mode: CompressionMode) -> Self {
let name = fuchsia_merkle::from_slice(&data).root();
Self { data: Type1Blob::generate(&data, mode), name }
}
}
/// A trait for filesystems that support reading and writing blobs.
#[async_trait]
pub trait BlobFilesystem: CacheClearableFilesystem {
/// Writes a blob to the filesystem.
///
/// Blobfs and Fxblob write blobs using different protocols. How a blob is written is
/// implemented in the filesystem so benchmarks don't have to know which protocol to use.
async fn write_blob(&self, blob: &DeliveryBlob);
/// Blobfs and Fxblob open and read blobs using different protocols. Benchmarks should remain
/// agnostic to which protocol is being used.
async fn get_vmo(&self, blob: &DeliveryBlob) -> zx::Vmo;
/// Returns the exposed dir of Blobfs or Fxblobs' blob volume.
fn exposed_dir(&self) -> &fio::DirectoryProxy;
}
enum FsType {
SingleVolume(ServingSingleVolumeFilesystem),
MultiVolume(ServingMultiVolumeFilesystem),
}
pub struct FsManagementFilesystemInstance {
fs: fs_management::filesystem::Filesystem,
serving_filesystem: Option<FsType>,
as_blob: bool,
// Keep the underlying block device alive for as long as we are using the filesystem.
_block_device: Box<dyn BlockDevice>,
}
impl FsManagementFilesystemInstance {
pub async fn new<FSC: FSConfig>(
config: FSC,
block_device: Box<dyn BlockDevice>,
as_blob: bool,
) -> Self {
let mut fs =
fs_management::filesystem::Filesystem::new(block_device.controller().clone(), config);
fs.format().await.expect("Failed to format the filesystem");
let serving_filesystem = if fs.config().is_multi_volume() {
let mut serving_filesystem =
fs.serve_multi_volume().await.expect("Failed to start the filesystem");
let vol = serving_filesystem
.create_volume(
"default",
MountOptions { crypt: fs.config().crypt_client().map(|c| c.into()), as_blob },
)
.await
.expect("Failed to create volume");
vol.bind_to_path(MOUNT_PATH).expect("Failed to bind the volume");
FsType::MultiVolume(serving_filesystem)
} else {
let mut serving_filesystem = fs.serve().await.expect("Failed to start the filesystem");
serving_filesystem.bind_to_path(MOUNT_PATH).expect("Failed to bind the filesystem");
FsType::SingleVolume(serving_filesystem)
};
Self {
fs,
serving_filesystem: Some(serving_filesystem),
_block_device: block_device,
as_blob,
}
}
fn exposed_dir(&self) -> &fio::DirectoryProxy {
let fs = self.serving_filesystem.as_ref().unwrap();
match fs {
FsType::SingleVolume(serving_filesystem) => serving_filesystem.exposed_dir(),
FsType::MultiVolume(serving_filesystem) => {
serving_filesystem.volume("default").unwrap().exposed_dir()
}
}
}
// Instead of returning the directory that is the data root for this filesystem, return the root
// that would be used for accessing top level service protocols.
fn exposed_services_dir(&self) -> &fio::DirectoryProxy {
let fs = self.serving_filesystem.as_ref().unwrap();
match fs {
FsType::SingleVolume(serving_filesystem) => serving_filesystem.exposed_dir(),
FsType::MultiVolume(serving_filesystem) => serving_filesystem.exposed_dir(),
}
}
}
#[async_trait]
impl Filesystem for FsManagementFilesystemInstance {
async fn shutdown(mut self) {
if let Some(fs) = self.serving_filesystem.take() {
match fs {
FsType::SingleVolume(fs) => fs.shutdown().await.expect("Failed to stop filesystem"),
FsType::MultiVolume(fs) => fs.shutdown().await.expect("Failed to stop filesystem"),
}
}
}
fn benchmark_dir(&self) -> &Path {
Path::new(MOUNT_PATH)
}
}
#[async_trait]
impl CacheClearableFilesystem for FsManagementFilesystemInstance {
async fn clear_cache(&mut self) {
// Remount the filesystem to guarantee that all cached data from reads and write is cleared.
let serving_filesystem = self.serving_filesystem.take().unwrap();
let serving_filesystem = match serving_filesystem {
FsType::SingleVolume(serving_filesystem) => {
serving_filesystem.shutdown().await.expect("Failed to stop the filesystem");
let mut serving_filesystem =
self.fs.serve().await.expect("Failed to start the filesystem");
serving_filesystem.bind_to_path(MOUNT_PATH).expect("Failed to bind the filesystem");
FsType::SingleVolume(serving_filesystem)
}
FsType::MultiVolume(serving_filesystem) => {
serving_filesystem.shutdown().await.expect("Failed to stop the filesystem");
let mut serving_filesystem =
self.fs.serve_multi_volume().await.expect("Failed to start the filesystem");
let vol = serving_filesystem
.open_volume(
"default",
MountOptions {
crypt: self.fs.config().crypt_client().map(|c| c.into()),
as_blob: self.as_blob,
},
)
.await
.expect("Failed to create volume");
vol.bind_to_path(MOUNT_PATH).expect("Failed to bind the volume");
FsType::MultiVolume(serving_filesystem)
}
};
self.serving_filesystem = Some(serving_filesystem);
}
}