| // Copyright 2020 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. |
| |
| pub mod blob; |
| pub mod operator; |
| |
| use { |
| anyhow::Error, |
| argh::FromArgs, |
| fasync::{Task, TimeoutExt}, |
| fidl_fuchsia_hardware_block_partition::Guid, |
| fs_management::{BlobLayout, Blobfs, Filesystem}, |
| fuchsia_async as fasync, |
| fuchsia_zircon::Vmo, |
| log::{debug, info, LevelFilter}, |
| operator::BlobfsOperator, |
| rand::{rngs::SmallRng, SeedableRng}, |
| std::{thread::sleep, time::Duration}, |
| stress_test_utils::{ |
| fvm::{get_volume_path, FvmInstance}, |
| random_seed, StdoutLogger, |
| }, |
| }; |
| |
| #[derive(Clone, Debug, FromArgs)] |
| /// Creates an instance of fvm and performs stressful operations on it |
| struct Args { |
| /// seed to use for this stressor instance |
| #[argh(option, short = 's')] |
| seed: Option<u128>, |
| |
| /// number of operations to complete before exiting. |
| #[argh(option, short = 'o')] |
| num_operations: Option<u64>, |
| |
| /// filter logging by level (off, error, warn, info, debug, trace) |
| #[argh(option, short = 'l')] |
| log_filter: Option<LevelFilter>, |
| |
| /// size of one block of the ramdisk (in bytes) |
| #[argh(option, default = "512")] |
| ramdisk_block_size: u64, |
| |
| /// number of blocks in the ramdisk |
| /// defaults to 106MiB ramdisk |
| #[argh(option, default = "217088")] |
| ramdisk_block_count: u64, |
| |
| /// size of one slice in FVM (in bytes) |
| #[argh(option, default = "32768")] |
| fvm_slice_size: u64, |
| |
| /// controls how often blobfs is disconnected by |
| /// crashing the block device. |
| /// disabled if set to 0. |
| #[argh(option, default = "0")] |
| disconnect_secs: u64, |
| |
| /// if set, the test runs for this time limit before exiting successfully. |
| #[argh(option, short = 't')] |
| time_limit_secs: Option<u64>, |
| } |
| |
| // All partitions in this test have their type set to this arbitrary GUID. |
| const TYPE_GUID: Guid = Guid { |
| value: [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf], |
| }; |
| |
| // The path to the blobfs filesystem in the test's namespace |
| const BLOBFS_MOUNT_PATH: &str = "/blobfs"; |
| |
| fn get_blobfs(volume_path: &str) -> Filesystem<Blobfs> { |
| let config = Blobfs { |
| /// Use the compact merkle layout for stress tests |
| blob_layout: Some(BlobLayout::Compact), |
| ..Blobfs::default() |
| }; |
| Filesystem::from_path(volume_path, config).unwrap() |
| } |
| |
| pub async fn run_test( |
| rng: SmallRng, |
| ramdisk_block_count: u64, |
| ramdisk_block_size: u64, |
| fvm_slice_size: u64, |
| num_operations: Option<u64>, |
| disconnect_secs: u64, |
| time_limit_secs: Option<u64>, |
| ) { |
| // Create the VMO that the ramdisk is backed by |
| let vmo_size = ramdisk_block_count * ramdisk_block_size; |
| let vmo = Vmo::create(vmo_size).unwrap(); |
| |
| // Initialize the ramdisk and setup FVM. |
| let mut instance = FvmInstance::new(true, &vmo, fvm_slice_size, ramdisk_block_size).await; |
| |
| // Create a blobfs volume |
| let volume_instance_guid = instance.new_volume("blobfs", TYPE_GUID).await; |
| |
| // Find the path to the volume |
| let block_path = instance.block_path(); |
| let mut volume_path = get_volume_path(block_path, &volume_instance_guid).await; |
| |
| // Initialize blobfs for the first time |
| let mut blobfs = get_blobfs(volume_path.to_str().unwrap()); |
| blobfs.format().unwrap(); |
| |
| if disconnect_secs > 0 { |
| Task::blocking(async move { |
| // Crash the block device every |disconnect_secs|. |
| loop { |
| { |
| // Start up blobfs |
| let mut blobfs = get_blobfs(volume_path.to_str().unwrap()); |
| blobfs.fsck().unwrap(); |
| blobfs.mount(BLOBFS_MOUNT_PATH).unwrap(); |
| |
| // Wait for the required amount of time |
| sleep(Duration::from_secs(disconnect_secs)); |
| |
| // Crash the old instance and replace it with a new instance. |
| // This will cause the component tree to be taken down abruptly. |
| debug!("Killing component manager"); |
| instance.kill_component_manager(); |
| |
| // Blobfs may not neatly terminate. Force kill the process. |
| let result = blobfs.kill(); |
| debug!("Blobfs kill result = {:?}", result); |
| } |
| |
| // Start up a new instance |
| instance = FvmInstance::new(false, &vmo, fvm_slice_size, ramdisk_block_size).await; |
| let block_path = instance.block_path(); |
| volume_path = get_volume_path(block_path, &volume_instance_guid).await; |
| } |
| }) |
| .detach(); |
| } else { |
| // Start up blobfs |
| blobfs.fsck().unwrap(); |
| blobfs.mount(BLOBFS_MOUNT_PATH).unwrap(); |
| } |
| |
| // Run the operator in a new thread |
| let operator_task = Task::blocking(async move { |
| let operator = BlobfsOperator::new(rng).await; |
| operator.do_random_operations(num_operations.unwrap_or(u64::MAX)).await; |
| }); |
| |
| if let Some(time_limit_secs) = time_limit_secs { |
| operator_task.on_timeout(Duration::from_secs(time_limit_secs), || (())).await; |
| } else { |
| operator_task.await; |
| }; |
| } |
| |
| #[fasync::run_singlethreaded] |
| async fn main() -> Result<(), Error> { |
| // Get arguments from command line |
| let args: Args = argh::from_env(); |
| |
| let filter = args.log_filter.unwrap_or(LevelFilter::Info); |
| StdoutLogger::init(filter); |
| |
| // Initialize seed |
| let seed = match args.seed { |
| Some(seed) => seed, |
| None => random_seed(), |
| }; |
| let rng = SmallRng::from_seed(seed.to_le_bytes()); |
| |
| info!("--------------------- stressor is starting -----------------------"); |
| info!("ENVIRONMENT = {:#?}", args); |
| info!("SEED FOR THIS INVOCATION = {}", seed); |
| info!("------------------------------------------------------------------"); |
| |
| // Setup a panic handler that prints out details of this invocation |
| let args_clone = args.clone(); |
| let seed = seed.clone(); |
| let default_panic_hook = std::panic::take_hook(); |
| std::panic::set_hook(Box::new(move |panic_info| { |
| eprintln!(""); |
| eprintln!("--------------------- stressor has crashed -----------------------"); |
| eprintln!("ENVIRONMENT = {:#?}", args_clone); |
| eprintln!("SEED FOR THIS INVOCATION = {}", seed); |
| eprintln!("------------------------------------------------------------------"); |
| eprintln!(""); |
| default_panic_hook(panic_info); |
| })); |
| |
| run_test( |
| rng, |
| args.ramdisk_block_count, |
| args.ramdisk_block_size, |
| args.fvm_slice_size, |
| args.num_operations, |
| args.disconnect_secs, |
| args.time_limit_secs, |
| ) |
| .await; |
| |
| info!("Stress test is exiting successfully!"); |
| |
| Ok(()) |
| } |