Rust 压力测试库

本文档介绍了如何使用 Rust 压力测试库编写压力测试。该库可以在以下位置找到://src/sys/lib/stress-test。它实现了测试循环以及运行这些测试所需的并发和同步基元。

编写压力测试

定义 GN build 目标

为您的测试定义 rustc_binaryfuchsia_componentfuchsia_test_package GN 构建目标:

rustc_binary("filesystem-stressor-bin") {
    deps = [
        ...
        "//src/sys/lib/stress-test",
        ...
    ]
    sources = [
        ...
    ]
}

fuchsia_component("filesystem-stressor") {
    deps = [ ":filesystem-stressor-bin" ]
    manifest = "meta/filesystem-stressor.cml"
    testonly = true
}

fuchsia_test_package("filesystem-stress-tests") {
  test_components = [
    ":filesystem-stressor"
  ]
}

写一个演员

每个 actor 都必须实现 Actor trait。actor trait 是一种由 ActorRunner 调用的 perform() 方法。被调用时,操作方必须仅执行一项操作并将其结果返回给运行程序。操作者必须存储执行操作所需的所有连接。

pub trait Actor: Sync + Send + 'static {
    // ActorRunner invokes this function, instructing the actor
    // to perform exactly one operation and return result.
    async fn perform(&mut self) -> Result<(), ActorError>;
}

执行者可以在返回结果中指明以下内容:

  • Ok(()):操作成功并计入全局操作计数。

  • Err(ActorError::DoNotCount):操作不得计入全局操作计数。

  • Err(ActorError::ResetEnvironment):必须重置环境,并且不得将操作计入全局操作计数。

当 actor 遇到意外错误时,它应该恐慌,从而停止测试。

由于操作者在同一环境中进行操作,因此其操作可能会发生冲突。例如,对于文件系统压力测试,操作者可能会对同一组文件执行操作。如果希望发生此类冲突,您必须设置操作方以妥善处理此类冲突。否则,操作者应恐慌,导致测试停止。

操作者可能会刻意破坏被测系统,要求重置环境。例如,对于文件系统压力测试,操作者可以随机切断文件系统与底层块设备之间的连接。在此示例中,其他操作者应使用 ActorError::ResetEnvironment 请求新环境,该环境将为所有操作者重新建立连接。

pub struct FilesystemActor {
    /// Store a connection to the root of filesystem here
    pub root_directory: Directory
    ...
}

impl FilesystemActor {
    pub fn new(root_directory: Directory) -> Self {
        ...
    }
}

#[async_trait]
impl Actor for FilesystemActor {
    async pub fn perform(&mut self) -> Result<(), ActorError> {
        // Choose exactly one operation to do on the filesystem
        // using the root_directory
        self.root_directory.delete_all_files();
    }
}

编写环境

环境提供压力测试的基本配置 - 退出标准、操作者和重置方法。

pub trait Environment: Send + Sync + Debug {
    /// Returns the target number of operations to complete before exiting
    fn target_operations(&self) -> Option<u64>;

    /// Returns the number of seconds to wait before exiting
    fn timeout_seconds(&self) -> Option<u64>;

    /// Return the runners for all the actors
    async fn actor_runners(&mut self) -> Vec<ActorRunner>;

    /// Reset the environment, when an actor requests one
    async fn reset(&mut self);
}

环境可以存储测试的其他配置。您可以通过命令行使用 argh crate 提供此配置。

操作者在运行程序和环境之间共享,因此必须封装为 Arc<Mutex<dyn Actor>>。运行程序会在操作者执行操作时持有锁。这意味着环境只能在操作之间获取操作者锁。

当操作者确定被测系统的当前实例已损坏时,系统会指示环境重置。环境应为被测系统创建一个新实例,并锁定操作方以更新其与新实例的连接。

环境还必须实现 Debug trait。压力测试会在测试开始时以及测试是否紧急时记录环境。通常的做法是输出对重现测试有价值的参数,例如所用的随机种子。

#[derive(Debug)]
pub struct FilesystemEnvironment {
    fs_actor: Arc<Mutex<FilesystemActor>>,
    seed: u64,
    ...
}

impl Environment {
    pub fn new() -> Self {
        ...
    }
}

#[async_trait]
impl Environment for FilesystemEnvironment {
    fn target_operations(&self) -> Option<u64> {
        // By specifying None here, the test will run without an operation limit
        None
    }

    fn timeout_seconds(&self) -> Option<u64> {
        // By specifying None here, the test will run without a time limit
        None
    }

    async fn actor_runners(&mut self) -> Vec<ActorRunner> {
        vec![
            ActorRunner::new(
                "filesystem_actor",  // debug name
                60,  // delay (in seconds) between each operation (0 means no delay)
                self.fs_actor.clone()), // actor
            )
        ]
    }

    async fn reset(&mut self) {
        // If the actor is performing an operation, this will remain
        // locked until the operation is complete.
        let actor = self.fs_actor.lock().await;

        // Now the environment can update the actor before it is run again.
        actor.root_directory = ...;

        // Releasing the lock will resume the runner.
    }
}

编写主函数

压力测试的主要功能很简单,因为大多数逻辑在环境和操作者中实现。使用主函数收集命令行参数(如果有)、初始化日志记录并设置日志严重性。

#[fuchsia::main]
async fn main() {
    // Create the environment
    let env = FilesystemEnvironment::new();

    // Run the test.
    // Depending on the exit criteria, this may never return.
    stress_test::run_test(env).await;
}

在本地运行压力测试

由于压力测试是 fuchsia_test_package 的一部分,因此运行压力测试最简单的方法之一是使用 fx test 命令:

fx test filesystem-stress-tests

如需使用自定义命令行参数运行测试,请使用 fx shell run

fx shell run fuchsia-pkg://fuchsia.com/filesystem-stress-tests#meta/filesystem-stressor.cm <args>

在基础架构上运行压力测试

基础架构通过附加到 fuchsia_test_packagefuchsia_unittest_package GN Build Target 的 stress-tests 标记标识压力测试。

fuchsia_test_package("filesystem-stress-tests") {
  test_components = [
    ":filesystem-stressor"
  ]
  test_specs = {
    environments = [
      {
        dimensions = {
          device_type = "QEMU"
        }
        tags = [ "stress-tests" ]
      },
    ]
  }
}

专用 core.x64-stress 构建器可识别这些测试,并将软件包中的每个测试组件运行最多 22 小时。

调试压力测试

该框架使用 Rust log crate 来记录消息。测试会在启动时以及测试是否紧急时记录环境对象。

--------------------- stressor is starting -----------------------
Environment {
    seed: 268479717856254664270968796173957499835,
    filesystem_actor: { ... }
    ...
}
------------------------------------------------------------------

如果启用了调试日志记录,则还会记录各个操作者操作和操作计数。

DEBUG: [0][filesystem_actor][389] Sleeping for 2 seconds
DEBUG: [0][filesystem_actor][389] Performing...
DEBUG: [0][filesystem_actor][389] Done!
DEBUG: Counters -> [total:403] {"filesystem_actor": 403}