Skip to main content

storage_benchmarks/
directory_benchmarks.rs

1// Copyright 2022 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 crate::{Benchmark, CacheClearableFilesystem, Filesystem, OperationDuration, OperationTimer};
6use async_trait::async_trait;
7use std::collections::VecDeque;
8use std::ffi::{CStr, CString};
9use std::mem::MaybeUninit;
10use std::ops::Range;
11use std::os::fd::RawFd;
12use std::os::unix::ffi::OsStringExt;
13use std::path::{Path, PathBuf};
14
15/// Describes the structure of a directory tree to be benchmarked.
16#[derive(Copy, Clone)]
17pub struct DirectoryTreeStructure {
18    /// The number of files that will be created within each directory.
19    pub files_per_directory: u64,
20
21    /// The number of subdirectories that will be created within each directory. No subdirectories
22    /// are created once |max_depth| is reached.
23    pub directories_per_directory: u64,
24
25    /// Specifies how many levels of directories to create.
26    pub max_depth: u32,
27}
28
29impl DirectoryTreeStructure {
30    /// Returns the maximum size of queue required to do a breadth first traversal of a directory
31    /// tree with this structure.
32    fn max_pending(&self) -> u64 {
33        self.directories_per_directory.pow(self.max_depth)
34    }
35
36    /// Creates a directory tree as described by this object starting from |root|.
37    fn create_directory_tree(&self, root: PathBuf) {
38        struct DirInfo {
39            path: PathBuf,
40            depth: u32,
41        }
42        let mut pending = VecDeque::new();
43        pending.push_back(DirInfo { path: root, depth: 0 });
44        while let Some(dir) = pending.pop_front() {
45            for i in 0..self.files_per_directory {
46                let path = dir.path.join(file_name(i));
47                std::fs::File::create(path).unwrap();
48            }
49            if self.max_depth == dir.depth {
50                continue;
51            }
52            for i in 0..self.directories_per_directory {
53                let path = dir.path.join(dir_name(i));
54                std::fs::create_dir(&path).unwrap();
55                pending.push_back(DirInfo { path, depth: dir.depth + 1 });
56            }
57        }
58    }
59
60    /// Generates a list of paths that would be created by `create_directory_tree`.
61    fn enumerate_paths(&self) -> Vec<PathBuf> {
62        let mut paths = Vec::new();
63        self.enumerate_paths_impl(Path::new(""), &mut paths);
64        paths
65    }
66
67    fn enumerate_paths_impl(&self, base: &Path, paths: &mut Vec<PathBuf>) {
68        for i in 0..self.files_per_directory {
69            paths.push(base.join(file_name(i)));
70        }
71        if self.max_depth == 0 {
72            return;
73        }
74        for i in 0..self.directories_per_directory {
75            let path = base.join(dir_name(i));
76            paths.push(path.clone());
77            Self { max_depth: self.max_depth - 1, ..*self }.enumerate_paths_impl(&path, paths);
78        }
79    }
80}
81
82/// A benchmark that measures how long it takes to walk a directory that should not already be
83/// cached in memory.
84#[derive(Clone)]
85pub struct WalkDirectoryTreeCold {
86    dts: DirectoryTreeStructure,
87    iterations: u64,
88}
89
90impl WalkDirectoryTreeCold {
91    pub fn new(dts: DirectoryTreeStructure, iterations: u64) -> Self {
92        Self { dts, iterations }
93    }
94}
95
96#[async_trait]
97impl<T: CacheClearableFilesystem> Benchmark<T> for WalkDirectoryTreeCold {
98    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
99        storage_trace::duration!(
100            "benchmark",
101            "WalkDirectoryTreeCold",
102            "files_per_directory" => self.dts.files_per_directory,
103            "directories_per_directory" => self.dts.directories_per_directory,
104            "max_depth" => self.dts.max_depth
105        );
106        let root = fs.benchmark_dir().to_path_buf();
107
108        self.dts.create_directory_tree(root.clone());
109        let max_pending = self.dts.max_pending() as usize;
110        let mut durations = Vec::new();
111        for i in 0..self.iterations {
112            fs.clear_cache().await;
113            storage_trace::duration!("benchmark", "WalkDirectoryTree", "iteration" => i);
114            let timer = OperationTimer::start();
115            walk_directory_tree(root.clone(), max_pending);
116            durations.push(timer.stop());
117        }
118        durations
119    }
120
121    fn name(&self) -> String {
122        format!(
123            "WalkDirectoryTreeCold/Files{}/Dirs{}/Depth{}",
124            self.dts.files_per_directory, self.dts.directories_per_directory, self.dts.max_depth
125        )
126    }
127}
128
129/// A benchmark that measures how long it takes to walk a directory that was recently accessed and
130/// may be cached in memory.
131#[derive(Clone)]
132pub struct WalkDirectoryTreeWarm {
133    dts: DirectoryTreeStructure,
134    iterations: u64,
135}
136
137impl WalkDirectoryTreeWarm {
138    pub fn new(dts: DirectoryTreeStructure, iterations: u64) -> Self {
139        Self { dts, iterations }
140    }
141}
142
143#[async_trait]
144impl<T: Filesystem> Benchmark<T> for WalkDirectoryTreeWarm {
145    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
146        storage_trace::duration!(
147            "benchmark",
148            "WalkDirectoryTreeWarm",
149            "files_per_directory" => self.dts.files_per_directory,
150            "directories_per_directory" => self.dts.directories_per_directory,
151            "max_depth" => self.dts.max_depth
152        );
153        let root = fs.benchmark_dir().to_path_buf();
154
155        self.dts.create_directory_tree(root.clone());
156        let max_pending = self.dts.max_pending() as usize;
157        let mut durations = Vec::new();
158        for i in 0..self.iterations {
159            storage_trace::duration!("benchmark", "WalkDirectoryTree", "iteration" => i);
160            let timer = OperationTimer::start();
161            walk_directory_tree(root.clone(), max_pending);
162            durations.push(timer.stop());
163        }
164        durations
165    }
166
167    fn name(&self) -> String {
168        format!(
169            "WalkDirectoryTreeWarm/Files{}/Dirs{}/Depth{}",
170            self.dts.files_per_directory, self.dts.directories_per_directory, self.dts.max_depth
171        )
172    }
173}
174
175/// Performs a breadth first traversal of the directory tree starting at |root|. As an optimization,
176/// |max_pending| can be supplied to pre-allocate the queue needed for the breadth first traversal.
177fn walk_directory_tree(root: PathBuf, max_pending: usize) {
178    let mut pending = VecDeque::new();
179    pending.reserve(max_pending);
180    pending.push_back(root);
181    while let Some(dir) = pending.pop_front() {
182        storage_trace::duration!("benchmark", "read_dir");
183        for entry in std::fs::read_dir(&dir).unwrap() {
184            let entry = entry.unwrap();
185            if entry.file_type().unwrap().is_dir() {
186                pending.push_back(entry.path());
187            }
188        }
189    }
190}
191
192/// A benchmark that measures how long it takes to call stat on a path to a file. A distinct file is
193/// used for each iteration.
194#[derive(Clone)]
195pub struct StatPath {
196    file_count: u64,
197}
198
199impl StatPath {
200    pub fn new() -> Self {
201        Self { file_count: 100 }
202    }
203}
204
205#[async_trait]
206impl<T: Filesystem> Benchmark<T> for StatPath {
207    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
208        storage_trace::duration!("benchmark", "StatPath");
209
210        let root = fs.benchmark_dir().to_path_buf();
211        for i in 0..self.file_count {
212            std::fs::File::create(root.join(file_name(i))).unwrap();
213        }
214
215        let root_fd =
216            open_path(&path_buf_to_c_string(root), libc::O_DIRECTORY | libc::O_RDONLY).unwrap();
217
218        let mut durations = Vec::with_capacity(self.file_count as usize);
219        for i in 0..self.file_count {
220            let path = path_buf_to_c_string(file_name(i));
221            storage_trace::duration!("benchmark", "stat", "file" => i);
222            let timer = OperationTimer::start();
223            stat_path_at(&root_fd, &path).unwrap();
224            durations.push(timer.stop());
225        }
226        durations
227    }
228
229    fn name(&self) -> String {
230        "StatPath".to_string()
231    }
232}
233
234/// A benchmark that measures how long it takes to open a file. A distinct file is used for each
235/// iteration.
236#[derive(Clone)]
237pub struct OpenFile {
238    file_count: u64,
239}
240
241impl OpenFile {
242    pub fn new() -> Self {
243        Self { file_count: 100 }
244    }
245}
246
247#[async_trait]
248impl<T: Filesystem> Benchmark<T> for OpenFile {
249    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
250        storage_trace::duration!("benchmark", "OpenFile");
251
252        let root = fs.benchmark_dir().to_path_buf();
253        for i in 0..self.file_count {
254            std::fs::File::create(root.join(file_name(i))).unwrap();
255        }
256
257        let root_fd =
258            open_path(&path_buf_to_c_string(root), libc::O_DIRECTORY | libc::O_RDONLY).unwrap();
259
260        let mut durations = Vec::with_capacity(self.file_count as usize);
261        for i in 0..self.file_count {
262            let path = path_buf_to_c_string(file_name(i));
263            // Pull the file outside of the trace so it doesn't capture the close call.
264            let _file = {
265                storage_trace::duration!("benchmark", "open", "file" => i);
266                let timer = OperationTimer::start();
267                let file = open_path_at(&root_fd, &path, libc::O_RDWR);
268                durations.push(timer.stop());
269                file
270            };
271        }
272        durations
273    }
274
275    fn name(&self) -> String {
276        "OpenFile".to_string()
277    }
278}
279
280/// A benchmark that measures how long it takes to create a file.
281#[derive(Clone)]
282pub struct CreateFile {
283    file_count: u64,
284}
285
286impl CreateFile {
287    pub fn new() -> Self {
288        Self { file_count: 100 }
289    }
290}
291
292#[async_trait]
293impl<T: Filesystem> Benchmark<T> for CreateFile {
294    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
295        storage_trace::duration!("benchmark", "CreateFile");
296
297        let root = fs.benchmark_dir().to_path_buf();
298        let root_fd =
299            open_path(&path_buf_to_c_string(root), libc::O_DIRECTORY | libc::O_RDONLY).unwrap();
300
301        let mut durations = Vec::with_capacity(self.file_count as usize);
302        for i in 0..self.file_count {
303            let path = path_buf_to_c_string(file_name(i));
304            // Pull the file outside of the trace so it doesn't capture the close call.
305            let _file = {
306                storage_trace::duration!("benchmark", "create", "file" => i);
307                let timer = OperationTimer::start();
308                let file = open_path_at(&root_fd, &path, libc::O_CREAT | libc::O_RDWR);
309                durations.push(timer.stop());
310                file
311            };
312        }
313        durations
314    }
315
316    fn name(&self) -> String {
317        "CreateFile".to_string()
318    }
319}
320
321/// A benchmark that measures how long it takes to unlink a file.
322#[derive(Clone)]
323pub struct UnlinkFile {
324    file_count: u64,
325}
326
327impl UnlinkFile {
328    pub fn new() -> Self {
329        Self { file_count: 100 }
330    }
331}
332
333#[async_trait]
334impl<T: Filesystem> Benchmark<T> for UnlinkFile {
335    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
336        storage_trace::duration!("benchmark", "UnlinkFile");
337
338        let root = fs.benchmark_dir().to_path_buf();
339        let root_fd =
340            open_path(&path_buf_to_c_string(root), libc::O_DIRECTORY | libc::O_RDONLY).unwrap();
341
342        for i in 0..self.file_count {
343            let path = path_buf_to_c_string(file_name(i));
344            let _file = open_path_at(&root_fd, &path, libc::O_CREAT | libc::O_RDWR).unwrap();
345        }
346
347        let mut durations = Vec::with_capacity(self.file_count as usize);
348        for i in 0..self.file_count {
349            let path = path_buf_to_c_string(file_name(i));
350            storage_trace::duration!("benchmark", "unlink", "file" => i);
351            let timer = OperationTimer::start();
352            unlink_path_at(&root_fd, &path).unwrap();
353            durations.push(timer.stop());
354        }
355        durations
356    }
357
358    fn name(&self) -> String {
359        "UnlinkFile".to_string()
360    }
361}
362
363/// A benchmark that measures how long it takes to open a file from a path that contains multiple
364/// directories. A distinct path and file is used for each iteration.
365#[derive(Clone)]
366pub struct OpenDeeplyNestedFile {
367    file_count: u64,
368    depth: u64,
369}
370
371impl OpenDeeplyNestedFile {
372    pub fn new() -> Self {
373        Self { file_count: 100, depth: 5 }
374    }
375
376    fn dir_path(&self, n: u64) -> PathBuf {
377        let mut path = PathBuf::new();
378        for i in 0..self.depth {
379            path = path.join(format!("dir-{:03}-{:03}", n, i));
380        }
381        path
382    }
383}
384
385#[async_trait]
386impl<T: Filesystem> Benchmark<T> for OpenDeeplyNestedFile {
387    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
388        storage_trace::duration!("benchmark", "OpenDeeplyNestedFile");
389
390        let root = fs.benchmark_dir().to_path_buf();
391        for i in 0..self.file_count {
392            let dir_path = root.join(self.dir_path(i));
393            std::fs::create_dir_all(&dir_path).unwrap();
394            std::fs::File::create(dir_path.join(file_name(i))).unwrap();
395        }
396
397        let root_fd =
398            open_path(&path_buf_to_c_string(root), libc::O_DIRECTORY | libc::O_RDONLY).unwrap();
399
400        let mut durations = Vec::with_capacity(self.file_count as usize);
401        for i in 0..self.file_count {
402            let path = path_buf_to_c_string(self.dir_path(i).join(file_name(i)));
403            // Pull the file outside of the trace so it doesn't capture the close call.
404            let _file = {
405                storage_trace::duration!("benchmark", "open", "file" => i);
406                let timer = OperationTimer::start();
407                let file = open_path_at(&root_fd, &path, libc::O_RDWR);
408                durations.push(timer.stop());
409                file
410            };
411        }
412        durations
413    }
414
415    fn name(&self) -> String {
416        "OpenDeeplyNestedFile".to_string()
417    }
418}
419
420/// A benchmark that mimics the filesystem usage pattern of `git status`.
421#[derive(Clone)]
422pub struct GitStatus {
423    dts: DirectoryTreeStructure,
424    iterations: u64,
425
426    /// The number of threads to use when stat'ing all of the files.
427    stat_threads: usize,
428}
429
430impl GitStatus {
431    pub fn new() -> Self {
432        // This will create 254 directories and 1020 files. A depth of 13 would create 16382
433        // directories and 65532 files which is close to the size of fuchsia.git.
434        let dts = DirectoryTreeStructure {
435            files_per_directory: 4,
436            directories_per_directory: 2,
437            max_depth: 7,
438        };
439
440        // Git uses many threads when stat'ing large repositories but using multiple threads in the
441        // benchmarks significantly increases the performance variation between runs.
442        Self { dts, iterations: 5, stat_threads: 1 }
443    }
444
445    /// Stat all of the paths in `paths`. This mimics git checking to see if any of the files in its
446    /// index have been modified.
447    fn stat_paths(&self, root: &OpenFd, paths: &Vec<CString>) {
448        storage_trace::duration!("benchmark", "GitStatus::stat_paths");
449        std::thread::scope(|scope| {
450            for thread in 0..self.stat_threads {
451                scope.spawn(move || {
452                    let paths = &paths;
453                    for path in batch_range(paths.len(), self.stat_threads, thread) {
454                        storage_trace::duration!("benchmark", "stat");
455                        stat_path_at(root, &paths[path]).unwrap();
456                    }
457                });
458            }
459        });
460    }
461
462    /// Performs a recursive depth first traversal of the directory tree.
463    fn walk_repo(&self, dir: &Path) {
464        storage_trace::duration!("benchmark", "GitStatus::walk_repo");
465        for entry in std::fs::read_dir(&dir).unwrap() {
466            let entry = entry.unwrap();
467            if entry.file_type().unwrap().is_dir() {
468                self.walk_repo(&entry.path());
469            }
470        }
471    }
472}
473
474#[async_trait]
475impl<T: Filesystem> Benchmark<T> for GitStatus {
476    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
477        let root = &fs.benchmark_dir().to_path_buf();
478
479        self.dts.create_directory_tree(root.clone());
480        let paths = self.dts.enumerate_paths().into_iter().map(path_buf_to_c_string).collect();
481
482        let root_fd =
483            open_path(&path_buf_to_c_string(root.clone()), libc::O_DIRECTORY | libc::O_RDONLY)
484                .unwrap();
485
486        let mut durations = Vec::new();
487        for i in 0..self.iterations {
488            storage_trace::duration!("benchmark", "GitStatus", "iteration" => i);
489            let timer = OperationTimer::start();
490            self.stat_paths(&root_fd, &paths);
491            self.walk_repo(&root);
492            durations.push(timer.stop());
493        }
494        durations
495    }
496
497    fn name(&self) -> String {
498        "GitStatus".to_owned()
499    }
500}
501
502pub fn file_name(n: u64) -> PathBuf {
503    format!("file-{:03}.txt", n).into()
504}
505
506pub fn dir_name(n: u64) -> PathBuf {
507    format!("dir-{:03}", n).into()
508}
509
510pub fn path_buf_to_c_string(path: PathBuf) -> CString {
511    CString::new(path.into_os_string().into_vec()).unwrap()
512}
513
514pub fn stat_path_at(dir: &OpenFd, path: &CStr) -> Result<libc::stat, std::io::Error> {
515    unsafe {
516        let mut stat = MaybeUninit::uninit();
517        // `libc::fstatat` is directly used instead of `std::fs::metadata` because
518        // `std::fs::metadata` uses `statx` on Linux.
519        let result =
520            libc::fstatat(dir.0, path.as_ptr(), stat.as_mut_ptr(), libc::AT_SYMLINK_NOFOLLOW);
521
522        if result == 0 { Ok(stat.assume_init()) } else { Err(std::io::Error::last_os_error()) }
523    }
524}
525
526/// Splits a collection of items into batches and returns the range of items that `batch_num`
527/// contains.
528fn batch_range(item_count: usize, batch_count: usize, batch_num: usize) -> Range<usize> {
529    let items_per_batch = (item_count + (batch_count - 1)) / batch_count;
530    let start = items_per_batch * batch_num;
531    let end = std::cmp::min(item_count, start + items_per_batch);
532    start..end
533}
534
535pub struct OpenFd(RawFd);
536
537impl Drop for OpenFd {
538    fn drop(&mut self) {
539        unsafe { libc::close(self.0) };
540    }
541}
542
543pub fn open_path(path: &CStr, flags: libc::c_int) -> Result<OpenFd, std::io::Error> {
544    let result = unsafe { libc::open(path.as_ptr(), flags) };
545    if result >= 0 { Ok(OpenFd(result)) } else { Err(std::io::Error::last_os_error()) }
546}
547
548pub fn open_path_at(
549    dir: &OpenFd,
550    path: &CStr,
551    flags: libc::c_int,
552) -> Result<OpenFd, std::io::Error> {
553    let result = unsafe { libc::openat(dir.0, path.as_ptr(), flags) };
554    if result >= 0 { Ok(OpenFd(result)) } else { Err(std::io::Error::last_os_error()) }
555}
556
557pub fn unlink_path_at(dir: &OpenFd, path: &CStr) -> Result<(), std::io::Error> {
558    let result = unsafe { libc::unlinkat(dir.0, path.as_ptr(), 0) };
559    if result == 0 { Ok(()) } else { Err(std::io::Error::last_os_error()) }
560}
561
562#[cfg(test)]
563mod tests {
564    use super::*;
565    use crate::testing::TestFilesystem;
566
567    const ITERATION_COUNT: u64 = 3;
568
569    #[derive(PartialEq, Debug)]
570    struct DirectoryTree {
571        files: u64,
572        directories: Vec<DirectoryTree>,
573    }
574
575    fn read_in_directory_tree(root: PathBuf) -> DirectoryTree {
576        let mut tree = DirectoryTree { files: 0, directories: vec![] };
577        for entry in std::fs::read_dir(root).unwrap() {
578            let entry = entry.unwrap();
579            if entry.file_type().unwrap().is_dir() {
580                tree.directories.push(read_in_directory_tree(entry.path()));
581            } else {
582                tree.files += 1;
583            }
584        }
585        tree
586    }
587
588    #[fuchsia::test]
589    async fn create_directory_tree_with_depth_of_zero_test() {
590        let test_fs = Box::new(TestFilesystem::new());
591        let dts = DirectoryTreeStructure {
592            files_per_directory: 3,
593            directories_per_directory: 2,
594            max_depth: 0,
595        };
596        let root = test_fs.benchmark_dir().to_owned();
597        dts.create_directory_tree(root.clone());
598
599        let directory_tree = read_in_directory_tree(root);
600        assert_eq!(directory_tree, DirectoryTree { files: 3, directories: vec![] });
601        test_fs.shutdown().await;
602    }
603
604    #[fuchsia::test]
605    async fn create_directory_tree_with_depth_of_two_test() {
606        let test_fs = Box::new(TestFilesystem::new());
607        let dts = DirectoryTreeStructure {
608            files_per_directory: 4,
609            directories_per_directory: 2,
610            max_depth: 2,
611        };
612        let root = test_fs.benchmark_dir().to_owned();
613        dts.create_directory_tree(root.clone());
614
615        let directory_tree = read_in_directory_tree(root);
616        assert_eq!(
617            directory_tree,
618            DirectoryTree {
619                files: 4,
620                directories: vec![
621                    DirectoryTree {
622                        files: 4,
623                        directories: vec![
624                            DirectoryTree { files: 4, directories: vec![] },
625                            DirectoryTree { files: 4, directories: vec![] }
626                        ]
627                    },
628                    DirectoryTree {
629                        files: 4,
630                        directories: vec![
631                            DirectoryTree { files: 4, directories: vec![] },
632                            DirectoryTree { files: 4, directories: vec![] }
633                        ]
634                    }
635                ]
636            }
637        );
638        test_fs.shutdown().await;
639    }
640
641    #[fuchsia::test]
642    fn enumerate_paths_test() {
643        let dts = DirectoryTreeStructure {
644            files_per_directory: 2,
645            directories_per_directory: 2,
646            max_depth: 0,
647        };
648        let paths = dts.enumerate_paths();
649        assert_eq!(paths, vec![PathBuf::from("file-000.txt"), PathBuf::from("file-001.txt")],);
650
651        let dts = DirectoryTreeStructure {
652            files_per_directory: 2,
653            directories_per_directory: 2,
654            max_depth: 1,
655        };
656        let paths = dts.enumerate_paths();
657        assert_eq!(
658            paths,
659            vec![
660                PathBuf::from("file-000.txt"),
661                PathBuf::from("file-001.txt"),
662                PathBuf::from("dir-000"),
663                PathBuf::from("dir-000/file-000.txt"),
664                PathBuf::from("dir-000/file-001.txt"),
665                PathBuf::from("dir-001"),
666                PathBuf::from("dir-001/file-000.txt"),
667                PathBuf::from("dir-001/file-001.txt"),
668            ],
669        );
670    }
671
672    #[fuchsia::test]
673    fn batch_range_test() {
674        assert_eq!(batch_range(10, 1, 0), 0..10);
675
676        assert_eq!(batch_range(10, 3, 0), 0..4);
677        assert_eq!(batch_range(10, 3, 1), 4..8);
678        assert_eq!(batch_range(10, 3, 2), 8..10);
679
680        assert_eq!(batch_range(10, 4, 3), 9..10);
681        assert_eq!(batch_range(12, 4, 3), 9..12);
682    }
683
684    #[fuchsia::test]
685    async fn walk_directory_tree_cold_test() {
686        let mut test_fs = Box::new(TestFilesystem::new());
687        let dts = DirectoryTreeStructure {
688            files_per_directory: 2,
689            directories_per_directory: 2,
690            max_depth: 2,
691        };
692        let results = WalkDirectoryTreeCold::new(dts, ITERATION_COUNT).run(test_fs.as_mut()).await;
693
694        assert_eq!(results.len(), ITERATION_COUNT as usize);
695        assert_eq!(test_fs.clear_cache_count().await, ITERATION_COUNT);
696        test_fs.shutdown().await;
697    }
698
699    #[fuchsia::test]
700    async fn walk_directory_tree_warm_test() {
701        let mut test_fs = Box::new(TestFilesystem::new());
702        let dts = DirectoryTreeStructure {
703            files_per_directory: 2,
704            directories_per_directory: 2,
705            max_depth: 2,
706        };
707        let results = WalkDirectoryTreeWarm::new(dts, ITERATION_COUNT).run(test_fs.as_mut()).await;
708
709        assert_eq!(results.len(), ITERATION_COUNT as usize);
710        assert_eq!(test_fs.clear_cache_count().await, 0);
711        test_fs.shutdown().await;
712    }
713
714    #[fuchsia::test]
715    async fn stat_path_test() {
716        let mut test_fs = Box::new(TestFilesystem::new());
717        let benchmark = StatPath { file_count: 5 };
718        let results = benchmark.run(test_fs.as_mut()).await;
719        assert_eq!(results.len(), 5);
720        assert_eq!(test_fs.clear_cache_count().await, 0);
721        test_fs.shutdown().await;
722    }
723
724    #[fuchsia::test]
725    async fn open_file_test() {
726        let mut test_fs = Box::new(TestFilesystem::new());
727        let benchmark = OpenFile { file_count: 5 };
728        let results = benchmark.run(test_fs.as_mut()).await;
729        assert_eq!(results.len(), 5);
730        assert_eq!(test_fs.clear_cache_count().await, 0);
731        test_fs.shutdown().await;
732    }
733
734    #[fuchsia::test]
735    async fn create_file_test() {
736        let mut test_fs = Box::new(TestFilesystem::new());
737        let benchmark = CreateFile { file_count: 5 };
738        let results = benchmark.run(test_fs.as_mut()).await;
739        assert_eq!(results.len(), 5);
740        assert_eq!(test_fs.clear_cache_count().await, 0);
741        test_fs.shutdown().await;
742    }
743
744    #[fuchsia::test]
745    async fn unlink_file_test() {
746        let mut test_fs = Box::new(TestFilesystem::new());
747        let benchmark = UnlinkFile { file_count: 5 };
748        let results = benchmark.run(test_fs.as_mut()).await;
749        assert_eq!(results.len(), 5);
750        assert_eq!(test_fs.clear_cache_count().await, 0);
751        test_fs.shutdown().await;
752    }
753
754    #[fuchsia::test]
755    async fn open_deeply_nested_file_test() {
756        let mut test_fs = Box::new(TestFilesystem::new());
757        let benchmark = OpenDeeplyNestedFile { file_count: 5, depth: 3 };
758        let results = benchmark.run(test_fs.as_mut()).await;
759        assert_eq!(results.len(), 5);
760        assert_eq!(test_fs.clear_cache_count().await, 0);
761        test_fs.shutdown().await;
762    }
763
764    #[fuchsia::test]
765    async fn git_status_test() {
766        let mut test_fs = Box::new(TestFilesystem::new());
767        let dts = DirectoryTreeStructure {
768            files_per_directory: 2,
769            directories_per_directory: 2,
770            max_depth: 2,
771        };
772        let benchmark = GitStatus { dts, iterations: ITERATION_COUNT, stat_threads: 1 };
773        let results = benchmark.run(test_fs.as_mut()).await;
774
775        assert_eq!(results.len(), ITERATION_COUNT as usize);
776        assert_eq!(test_fs.clear_cache_count().await, 0);
777        test_fs.shutdown().await;
778    }
779}