1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Copyright 2022 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 {
    crate::BlockDeviceFactory,
    async_trait::async_trait,
    std::{
        future::Future,
        path::{Path, PathBuf},
        pin::Pin,
    },
};

/// A trait for creating `Filesystem`s.
#[async_trait]
pub trait FilesystemConfig: Send + Sync {
    /// The concrete type of the filesystem started by `start_filesystem`. It must at least
    /// implement the `Filesystem` trait.
    type Filesystem: Filesystem;

    /// Starts an instance of the filesystem.
    async fn start_filesystem(
        &self,
        block_device_factory: &dyn BlockDeviceFactory,
    ) -> Self::Filesystem;

    /// The name of the filesystem. This is used for filtering benchmarks and outputting results.
    fn name(&self) -> String;
}

/// A trait representing a mounted filesystem that benchmarks will be run against.
#[async_trait]
pub trait Filesystem: Send + Sync + BoxedFilesystem {
    /// Cleans up the filesystem after a benchmark has run. Filesystem is unusable after this call.
    async fn shutdown(self);

    /// Path to where the filesystem is located in the current process's namespace. All benchmark
    /// operations should happen within this directory.
    fn benchmark_dir(&self) -> &Path;
}

/// Helper trait for shutting down `Box<dyn Filesystem>` objects.
pub trait BoxedFilesystem: Send + Sync {
    /// `Filesystem::shutdown` takes the filesystem by value which doesn't work for `Box<&dyn
    /// Filesystem>` because the filesystem type isn't known at compile time. Taking the filesystem
    /// as `Box<Self>` works and the type of the filesystem is now known so the
    /// `Filesystem::shutdown` method can be called.
    fn shutdown_boxed<'a>(self: Box<Self>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
    where
        Self: 'a;
}

impl<T: Filesystem> BoxedFilesystem for T {
    fn shutdown_boxed<'a>(self: Box<Self>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
    where
        Self: 'a,
    {
        (*self).shutdown()
    }
}

/// A trait for filesystems that are able to clear their caches.
#[async_trait]
pub trait CacheClearableFilesystem: Filesystem {
    /// Clears all cached data in the filesystem. This method is used in "cold" benchmarks to
    /// ensure that the filesystem isn't using cached data from the setup phase in the benchmark
    /// phase.
    async fn clear_cache(&mut self);
}

/// A `FilesystemConfig` for a filesystem that is already present in the process's namespace.
#[derive(Clone)]
pub struct MountedFilesystem {
    /// Path to an existing filesystem.
    dir: PathBuf,

    /// Name of the filesystem that backs `dir`.
    name: String,
}

impl MountedFilesystem {
    pub fn new<P: Into<PathBuf>>(dir: P, name: String) -> Self {
        Self { dir: dir.into(), name }
    }
}

#[async_trait]
impl FilesystemConfig for MountedFilesystem {
    type Filesystem = MountedFilesystemInstance;
    async fn start_filesystem(
        &self,
        _block_device_factory: &dyn BlockDeviceFactory,
    ) -> MountedFilesystemInstance {
        // Create a new directory within the existing filesystem to make it easier for cleaning up
        // afterwards.
        let path = self.dir.join("benchmark");
        std::fs::create_dir(&path).unwrap_or_else(|e| {
            panic!("failed to created benchmark directory '{}': {:?}", path.display(), e)
        });
        MountedFilesystemInstance::new(path)
    }

    fn name(&self) -> String {
        self.name.clone()
    }
}

/// A `Filesystem` instance for a filesystem that is already present in the process's namespace.
pub struct MountedFilesystemInstance {
    dir: PathBuf,
}

impl MountedFilesystemInstance {
    pub fn new<P: Into<PathBuf>>(dir: P) -> Self {
        Self { dir: dir.into() }
    }
}

#[async_trait]
impl Filesystem for MountedFilesystemInstance {
    async fn shutdown(self) {
        std::fs::remove_dir_all(self.dir).expect("Failed to remove benchmark directory");
    }

    fn benchmark_dir(&self) -> &Path {
        self.dir.as_path()
    }
}