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