Skip to main content

storage_benchmarks/
io_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 rand::seq::SliceRandom as _;
8use rand::{Rng, SeedableRng as _};
9use rand_xorshift::XorShiftRng;
10use std::fs::OpenOptions;
11use std::io::{Seek, SeekFrom, Write};
12use std::os::unix::fs::FileExt;
13use std::os::unix::io::AsRawFd;
14
15const RNG_SEED: u64 = 0xda782a0c3ce1819a;
16/// How many blocks to skip after each used block in sparse read benchmarks. This is set to thwart
17/// the effects of readahead on these benchmarks. As high as we can go while staying under the file
18/// size limit of 4GiB in minfs.
19const BLOCK_SKIP: usize = 255;
20
21/// A benchmark that measures how long `read` calls take to a file that should not already be cached
22/// in memory.
23#[derive(Clone)]
24pub struct ReadSequentialCold {
25    op_size: usize,
26    op_count: usize,
27    read_only: bool,
28}
29
30impl ReadSequentialCold {
31    pub fn new(op_size: usize, op_count: usize) -> Self {
32        Self { op_size, op_count, read_only: false }
33    }
34
35    pub fn new_read_only(op_size: usize, op_count: usize) -> Self {
36        Self { op_size, op_count, read_only: true }
37    }
38}
39
40#[async_trait]
41impl<T: CacheClearableFilesystem> Benchmark<T> for ReadSequentialCold {
42    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
43        storage_trace::duration!(
44            "benchmark",
45            "ReadSequentialCold",
46            "op_size" => self.op_size,
47            "op_count" => self.op_count
48        );
49        let file_path = fs.benchmark_dir().join("file");
50
51        // Setup
52        if !self.read_only {
53            let mut file =
54                OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
55            write_file(&mut file, self.op_size, self.op_count);
56            std::mem::drop(file);
57        }
58        fs.clear_cache().await;
59
60        // Benchmark
61        let mut file = OpenOptions::new().read(true).open(&file_path).unwrap();
62        read_sequential(&mut file, self.op_size, self.op_count)
63    }
64
65    fn name(&self) -> String {
66        format!("ReadSequentialCold/{}", self.op_size)
67    }
68}
69
70/// A benchmark that measures how long `read` calls take to a file that should already be cached in
71/// memory.
72#[derive(Clone)]
73pub struct ReadSequentialWarm {
74    op_size: usize,
75    op_count: usize,
76    read_only: bool,
77}
78
79impl ReadSequentialWarm {
80    pub fn new(op_size: usize, op_count: usize) -> Self {
81        Self { op_size, op_count, read_only: false }
82    }
83
84    pub fn new_read_only(op_size: usize, op_count: usize) -> Self {
85        Self { op_size, op_count, read_only: true }
86    }
87}
88
89#[async_trait]
90impl<T: Filesystem> Benchmark<T> for ReadSequentialWarm {
91    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
92        storage_trace::duration!(
93            "benchmark",
94            "ReadSequentialWarm",
95            "op_size" => self.op_size,
96            "op_count" => self.op_count
97        );
98        let file_path = fs.benchmark_dir().join("file");
99
100        // Setup
101        let mut file = if self.read_only {
102            let mut file = OpenOptions::new().read(true).open(&file_path).unwrap();
103            let _ = read_sequential(&mut file, self.op_size, self.op_count);
104            file.seek(SeekFrom::Start(0)).unwrap();
105            file
106        } else {
107            let mut file = OpenOptions::new()
108                .write(true)
109                .read(true)
110                .create_new(true)
111                .open(&file_path)
112                .unwrap();
113            write_file(&mut file, self.op_size, self.op_count);
114            file.seek(SeekFrom::Start(0)).unwrap();
115            file
116        };
117
118        // Benchmark
119        read_sequential(&mut file, self.op_size, self.op_count)
120    }
121
122    fn name(&self) -> String {
123        format!("ReadSequentialWarm/{}", self.op_size)
124    }
125}
126
127/// A benchmark that measures how long random `pread` calls take to a file that should not already
128/// be cached in memory.
129#[derive(Clone)]
130pub struct ReadRandomCold {
131    op_size: usize,
132    op_count: usize,
133    read_only: bool,
134}
135
136impl ReadRandomCold {
137    pub fn new(op_size: usize, op_count: usize) -> Self {
138        Self { op_size, op_count, read_only: false }
139    }
140
141    pub fn new_read_only(op_size: usize, op_count: usize) -> Self {
142        Self { op_size, op_count, read_only: true }
143    }
144}
145
146#[async_trait]
147impl<T: CacheClearableFilesystem> Benchmark<T> for ReadRandomCold {
148    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
149        storage_trace::duration!(
150            "benchmark",
151            "ReadRandomCold",
152            "op_size" => self.op_size,
153            "op_count" => self.op_count
154        );
155        let file_path = fs.benchmark_dir().join("file");
156
157        // Setup
158        if !self.read_only {
159            let mut file =
160                OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
161            write_file(&mut file, self.op_size, self.op_count);
162            std::mem::drop(file);
163        }
164        fs.clear_cache().await;
165
166        // Benchmark
167        let mut rng = XorShiftRng::seed_from_u64(RNG_SEED);
168        let mut file = OpenOptions::new().read(true).open(&file_path).unwrap();
169        read_random(&mut file, self.op_size, self.op_count, &mut rng)
170    }
171
172    fn name(&self) -> String {
173        format!("ReadRandomCold/{}", self.op_size)
174    }
175}
176
177/// A benchmark that measures how long `pread` calls take to a sparse file that should not already
178/// be cached in memory. The file's sparseness is designed to defeat the gains of readahead without
179/// making an extremely large file, but will also result in testing access to many small extents.
180#[derive(Clone)]
181pub struct ReadSparseCold {
182    op_size: usize,
183    op_count: usize,
184}
185
186impl ReadSparseCold {
187    pub fn new(op_size: usize, op_count: usize) -> Self {
188        Self { op_size, op_count }
189    }
190}
191
192#[async_trait]
193impl<T: CacheClearableFilesystem> Benchmark<T> for ReadSparseCold {
194    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
195        storage_trace::duration!(
196            "benchmark",
197            "ReadSparseCold",
198            "op_size" => self.op_size,
199            "op_count" => self.op_count
200        );
201        let file_path = fs.benchmark_dir().join("file");
202
203        // Setup
204        let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
205        write_sparse_file(&mut file, self.op_size, self.op_count, BLOCK_SKIP);
206        std::mem::drop(file);
207        fs.clear_cache().await;
208
209        // Benchmark
210        let mut file = OpenOptions::new().read(true).open(&file_path).unwrap();
211        read_sparse(&mut file, self.op_size, self.op_count, BLOCK_SKIP)
212    }
213
214    fn name(&self) -> String {
215        format!("ReadSparseCold/{}", self.op_size)
216    }
217}
218
219/// A benchmark that measures how long random `pread` calls take to a file that should already be
220/// cached in memory.
221#[derive(Clone)]
222pub struct ReadRandomWarm {
223    op_size: usize,
224    op_count: usize,
225    read_only: bool,
226}
227
228impl ReadRandomWarm {
229    pub fn new(op_size: usize, op_count: usize) -> Self {
230        Self { op_size, op_count, read_only: false }
231    }
232
233    pub fn new_read_only(op_size: usize, op_count: usize) -> Self {
234        Self { op_size, op_count, read_only: true }
235    }
236}
237
238#[async_trait]
239impl<T: Filesystem> Benchmark<T> for ReadRandomWarm {
240    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
241        storage_trace::duration!(
242            "benchmark",
243            "ReadRandomWarm",
244            "op_size" => self.op_size,
245            "op_count" => self.op_count
246        );
247        let file_path = fs.benchmark_dir().join("file");
248
249        // Setup
250        let mut file = if self.read_only {
251            let mut file = OpenOptions::new().read(true).open(&file_path).unwrap();
252            let _ = read_sequential(&mut file, self.op_size, self.op_count);
253            file
254        } else {
255            let mut file = OpenOptions::new()
256                .write(true)
257                .read(true)
258                .create_new(true)
259                .open(&file_path)
260                .unwrap();
261            write_file(&mut file, self.op_size, self.op_count);
262            file
263        };
264
265        // Benchmark
266        let mut rng = XorShiftRng::seed_from_u64(RNG_SEED);
267        read_random(&mut file, self.op_size, self.op_count, &mut rng)
268    }
269
270    fn name(&self) -> String {
271        format!("ReadRandomWarm/{}", self.op_size)
272    }
273}
274
275/// A benchmark that measures how long `write` calls take to a new file.
276#[derive(Clone)]
277pub struct WriteSequentialCold {
278    op_size: usize,
279    op_count: usize,
280}
281
282impl WriteSequentialCold {
283    pub fn new(op_size: usize, op_count: usize) -> Self {
284        Self { op_size, op_count }
285    }
286}
287
288#[async_trait]
289impl<T: Filesystem> Benchmark<T> for WriteSequentialCold {
290    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
291        storage_trace::duration!(
292            "benchmark",
293            "WriteSequentialCold",
294            "op_size" => self.op_size,
295            "op_count" => self.op_count
296        );
297        let file_path = fs.benchmark_dir().join("file");
298        let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
299        write_sequential(&mut file, self.op_size, self.op_count)
300    }
301
302    fn name(&self) -> String {
303        format!("WriteSequentialCold/{}", self.op_size)
304    }
305}
306
307/// A benchmark that measures how long `write` calls take when overwriting a file that should
308/// already be in memory.
309#[derive(Clone)]
310pub struct WriteSequentialWarm {
311    op_size: usize,
312    op_count: usize,
313}
314
315impl WriteSequentialWarm {
316    pub fn new(op_size: usize, op_count: usize) -> Self {
317        Self { op_size, op_count }
318    }
319}
320
321#[async_trait]
322impl<T: Filesystem> Benchmark<T> for WriteSequentialWarm {
323    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
324        storage_trace::duration!(
325            "benchmark",
326            "WriteSequentialWarm",
327            "op_size" => self.op_size,
328            "op_count" => self.op_count
329        );
330        let file_path = fs.benchmark_dir().join("file");
331
332        // Setup
333        let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
334        write_file(&mut file, self.op_size, self.op_count);
335        file.seek(SeekFrom::Start(0)).unwrap();
336
337        // Benchmark
338        write_sequential(&mut file, self.op_size, self.op_count)
339    }
340
341    fn name(&self) -> String {
342        format!("WriteSequentialWarm/{}", self.op_size)
343    }
344}
345
346/// A benchmark that measures how long random `pwrite` calls take to a new file.
347#[derive(Clone)]
348pub struct WriteRandomCold {
349    op_size: usize,
350    op_count: usize,
351}
352
353impl WriteRandomCold {
354    pub fn new(op_size: usize, op_count: usize) -> Self {
355        Self { op_size, op_count }
356    }
357}
358
359#[async_trait]
360impl<T: Filesystem> Benchmark<T> for WriteRandomCold {
361    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
362        storage_trace::duration!(
363            "benchmark",
364            "WriteRandomCold",
365            "op_size" => self.op_size,
366            "op_count" => self.op_count
367        );
368        let file_path = fs.benchmark_dir().join("file");
369        let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
370        let mut rng = XorShiftRng::seed_from_u64(RNG_SEED);
371        write_random(&mut file, self.op_size, self.op_count, &mut rng)
372    }
373
374    fn name(&self) -> String {
375        format!("WriteRandomCold/{}", self.op_size)
376    }
377}
378
379/// A benchmark that measures how long random `pwrite` calls take when overwriting a file that
380/// should already be in memory.
381#[derive(Clone)]
382pub struct WriteRandomWarm {
383    op_size: usize,
384    op_count: usize,
385}
386
387impl WriteRandomWarm {
388    pub fn new(op_size: usize, op_count: usize) -> Self {
389        Self { op_size, op_count }
390    }
391}
392
393#[async_trait]
394impl<T: Filesystem> Benchmark<T> for WriteRandomWarm {
395    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
396        storage_trace::duration!(
397            "benchmark",
398            "WriteRandomWarm",
399            "op_size" => self.op_size,
400            "op_count" => self.op_count
401        );
402        let file_path = fs.benchmark_dir().join("file");
403
404        // Setup
405        let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
406        write_sequential(&mut file, self.op_size, self.op_count);
407
408        // Benchmark
409        let mut rng = XorShiftRng::seed_from_u64(RNG_SEED);
410        write_random(&mut file, self.op_size, self.op_count, &mut rng)
411    }
412
413    fn name(&self) -> String {
414        format!("WriteRandomWarm/{}", self.op_size)
415    }
416}
417
418/// A benchmark that measures how long 'write` and `fsync` calls take to a new file.
419#[derive(Clone)]
420pub struct WriteSequentialFsyncCold {
421    op_size: usize,
422    op_count: usize,
423}
424
425impl WriteSequentialFsyncCold {
426    pub fn new(op_size: usize, op_count: usize) -> Self {
427        Self { op_size, op_count }
428    }
429}
430
431#[async_trait]
432impl<T: Filesystem> Benchmark<T> for WriteSequentialFsyncCold {
433    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
434        storage_trace::duration!(
435            "benchmark",
436            "WriteSequentialFsyncCold",
437            "op_size" => self.op_size,
438            "op_count" => self.op_count
439        );
440        let file_path = fs.benchmark_dir().join("file");
441        let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
442        write_sequential_fsync(&mut file, self.op_size, self.op_count)
443    }
444
445    fn name(&self) -> String {
446        format!("WriteSequentialFsyncCold/{}", self.op_size)
447    }
448}
449
450/// A benchmark that measures how long `write` and `fsync` calls take when overwriting a
451/// file that should already be in memory.
452#[derive(Clone)]
453pub struct WriteSequentialFsyncWarm {
454    op_size: usize,
455    op_count: usize,
456}
457
458impl WriteSequentialFsyncWarm {
459    pub fn new(op_size: usize, op_count: usize) -> Self {
460        Self { op_size, op_count }
461    }
462}
463
464#[async_trait]
465impl<T: Filesystem> Benchmark<T> for WriteSequentialFsyncWarm {
466    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
467        storage_trace::duration!(
468            "benchmark",
469            "WriteSequentialFsyncWarm",
470            "op_size" => self.op_size,
471            "op_count" => self.op_count
472        );
473        let file_path = fs.benchmark_dir().join("file");
474
475        // Setup
476        let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
477        write_sequential(&mut file, self.op_size, self.op_count);
478        // To measure the exact performance of fsync, the previous written data must be synchronized.
479        assert_eq!(unsafe { libc::fsync(file.as_raw_fd()) }, 0);
480        file.seek(SeekFrom::Start(0)).unwrap();
481
482        // Benchmark
483        write_sequential_fsync(&mut file, self.op_size, self.op_count)
484    }
485
486    fn name(&self) -> String {
487        format!("WriteSequentialFsyncWarm/{}", self.op_size)
488    }
489}
490
491/// A benchmark that measures how long 'write` and `fsync` calls take to a new file.
492#[derive(Clone)]
493pub struct WriteRandomFsyncCold {
494    op_size: usize,
495    op_count: usize,
496}
497
498impl WriteRandomFsyncCold {
499    pub fn new(op_size: usize, op_count: usize) -> Self {
500        Self { op_size, op_count }
501    }
502}
503
504#[async_trait]
505impl<T: Filesystem> Benchmark<T> for WriteRandomFsyncCold {
506    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
507        storage_trace::duration!(
508            "benchmark",
509            "WriteRandomFsyncCold",
510            "op_size" => self.op_size,
511            "op_count" => self.op_count
512        );
513        let file_path = fs.benchmark_dir().join("file");
514        let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
515        let mut rng = XorShiftRng::seed_from_u64(RNG_SEED);
516        write_random_fsync(&mut file, self.op_size, self.op_count, &mut rng)
517    }
518
519    fn name(&self) -> String {
520        format!("WriteRandomFsyncCold/{}", self.op_size)
521    }
522}
523
524/// A benchmark that measures how long `write` and `fsync` calls take when overwriting a
525/// file that should already be in memory.
526#[derive(Clone)]
527pub struct WriteRandomFsyncWarm {
528    op_size: usize,
529    op_count: usize,
530}
531
532impl WriteRandomFsyncWarm {
533    pub fn new(op_size: usize, op_count: usize) -> Self {
534        Self { op_size, op_count }
535    }
536}
537
538#[async_trait]
539impl<T: Filesystem> Benchmark<T> for WriteRandomFsyncWarm {
540    async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
541        storage_trace::duration!(
542            "benchmark",
543            "WriteRandomFsyncWarm",
544            "op_size" => self.op_size,
545            "op_count" => self.op_count
546        );
547        let file_path = fs.benchmark_dir().join("file");
548
549        // Setup
550        let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
551        write_sequential(&mut file, self.op_size, self.op_count);
552        // To measure the exact performance of fsync, the previous written data must be synchronized.
553        assert_eq!(unsafe { libc::fsync(file.as_raw_fd()) }, 0);
554        file.seek(SeekFrom::Start(0)).unwrap();
555        let mut rng = XorShiftRng::seed_from_u64(RNG_SEED);
556
557        // Benchmark
558        write_random_fsync(&mut file, self.op_size, self.op_count, &mut rng)
559    }
560
561    fn name(&self) -> String {
562        format!("WriteRandomFsyncWarm/{}", self.op_size)
563    }
564}
565
566fn write_file<F: Write + FileExt>(file: &mut F, op_size: usize, op_count: usize) {
567    write_sparse_file(file, op_size, op_count, 0);
568}
569
570fn write_sparse_file<F: Write + FileExt>(
571    file: &mut F,
572    op_size: usize,
573    op_count: usize,
574    block_skip: usize,
575) {
576    let data = vec![0xAB; op_size];
577    let mut offset: u64 = 0;
578    for _ in 0..op_count {
579        assert_eq!(file.write_at(&data, offset).unwrap(), op_size);
580        offset += (op_size * (block_skip + 1)) as u64;
581    }
582}
583
584/// Makes `op_count` `read` calls to `file`, each for `op_size` bytes.
585fn read_sequential<F: AsRawFd>(
586    file: &mut F,
587    op_size: usize,
588    op_count: usize,
589) -> Vec<OperationDuration> {
590    let mut data = vec![0; op_size];
591    let mut durations = Vec::new();
592    let fd = file.as_raw_fd();
593    for i in 0..op_count {
594        storage_trace::duration!("benchmark", "read", "op_number" => i);
595        let timer = OperationTimer::start();
596        let result = unsafe { libc::read(fd, data.as_mut_ptr() as *mut libc::c_void, data.len()) };
597        durations.push(timer.stop());
598        assert_eq!(result, op_size as isize);
599    }
600    durations
601}
602
603/// Makes `op_count` `pread` calls to `file`, each for `op_size` bytes `block_skip` * `op_size`
604/// bytes apart.
605fn read_sparse<F: AsRawFd>(
606    file: &mut F,
607    op_size: usize,
608    op_count: usize,
609    block_skip: usize,
610) -> Vec<OperationDuration> {
611    let mut data = vec![0; op_size];
612    let mut durations = Vec::new();
613    let fd = file.as_raw_fd();
614    let sparse_offset = ((1 + block_skip) * op_size) as i64;
615    for i in 0..op_count as i64 {
616        storage_trace::duration!("benchmark", "pread", "op_number" => i);
617        let timer = OperationTimer::start();
618        let result = unsafe {
619            libc::pread(fd, data.as_mut_ptr() as *mut libc::c_void, data.len(), i * sparse_offset)
620        };
621        durations.push(timer.stop());
622        assert_eq!(result, op_size as isize);
623    }
624    durations
625}
626
627/// Makes `op_count` `write` calls to `file`, each containing `op_size` bytes.
628fn write_sequential<F: AsRawFd>(
629    file: &mut F,
630    op_size: usize,
631    op_count: usize,
632) -> Vec<OperationDuration> {
633    let data = vec![0xAB; op_size];
634    let mut durations = Vec::new();
635    let fd = file.as_raw_fd();
636    for i in 0..op_count {
637        storage_trace::duration!("benchmark", "write", "op_number" => i);
638        let timer = OperationTimer::start();
639        let result = unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, data.len()) };
640        durations.push(timer.stop());
641        assert_eq!(result, op_size as isize);
642    }
643    durations
644}
645
646/// Makes `op_count` `write` calls to `file`, each containing `op_size` bytes.
647/// After write, call `fsync` and sync with disk(non-volatile medium).
648fn write_sequential_fsync<F: AsRawFd>(
649    file: &mut F,
650    op_size: usize,
651    op_count: usize,
652) -> Vec<OperationDuration> {
653    let data = vec![0xAB; op_size];
654    let mut durations = Vec::new();
655    let fd = file.as_raw_fd();
656    for i in 0..op_count {
657        storage_trace::duration!("benchmark", "write", "op_number" => i);
658        let timer = OperationTimer::start();
659        let write_result =
660            unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, data.len()) };
661        let fsync_result = unsafe { libc::fsync(fd) };
662        durations.push(timer.stop());
663        assert_eq!(write_result, op_size as isize);
664        assert_eq!(fsync_result, 0);
665    }
666    durations
667}
668
669fn create_random_offsets<R: Rng>(op_size: usize, op_count: usize, rng: &mut R) -> Vec<libc::off_t> {
670    let op_count = op_count as libc::off_t;
671    let op_size = op_size as libc::off_t;
672    let mut offsets: Vec<libc::off_t> = (0..op_count).map(|offset| offset * op_size).collect();
673    offsets.shuffle(rng);
674    offsets
675}
676
677/// Reads the first `op_size * op_count` bytes in `file` by making `op_count` `pread` calls, each
678/// `pread` call reads `op_size` bytes. The offset order for the `pread` calls is randomized using
679/// `rng`.
680fn read_random<F: AsRawFd, R: Rng>(
681    file: &mut F,
682    op_size: usize,
683    op_count: usize,
684    rng: &mut R,
685) -> Vec<OperationDuration> {
686    let offsets = create_random_offsets(op_size, op_count, rng);
687    let mut data = vec![0xAB; op_size];
688    let mut durations = Vec::new();
689    let fd = file.as_raw_fd();
690    for (i, offset) in offsets.iter().enumerate() {
691        storage_trace::duration!("benchmark", "pread", "op_number" => i, "offset" => *offset);
692        let timer = OperationTimer::start();
693        let result =
694            unsafe { libc::pread(fd, data.as_mut_ptr() as *mut libc::c_void, data.len(), *offset) };
695        durations.push(timer.stop());
696        assert_eq!(result, op_size as isize);
697    }
698    durations
699}
700
701/// Overwrites the first `op_size * op_count` bytes in `file` by making `op_count` `pwrite` calls,
702/// each `pwrite` call writes `op_size` bytes. The offset order for the `pwrite` calls is
703/// randomized using `rng`.
704fn write_random<F: AsRawFd, R: Rng>(
705    file: &mut F,
706    op_size: usize,
707    op_count: usize,
708    rng: &mut R,
709) -> Vec<OperationDuration> {
710    let offsets = create_random_offsets(op_size, op_count, rng);
711    let data = vec![0xAB; op_size];
712    let mut durations = Vec::new();
713    let fd = file.as_raw_fd();
714    for (i, offset) in offsets.iter().enumerate() {
715        storage_trace::duration!("benchmark", "pwrite", "op_number" => i, "offset" => *offset);
716        let timer = OperationTimer::start();
717        let result =
718            unsafe { libc::pwrite(fd, data.as_ptr() as *const libc::c_void, data.len(), *offset) };
719        durations.push(timer.stop());
720        assert_eq!(result, op_size as isize);
721    }
722    durations
723}
724
725/// Overwrites the first `op_size * op_count` bytes in `file` by making `op_count` `pwrite` calls,
726/// each `pwrite` call writes `op_size` bytes. The offset order for the `pwrite` calls is
727/// randomized using `rng`.
728fn write_random_fsync<F: AsRawFd, R: Rng>(
729    file: &mut F,
730    op_size: usize,
731    op_count: usize,
732    rng: &mut R,
733) -> Vec<OperationDuration> {
734    let offsets = create_random_offsets(op_size, op_count, rng);
735    let data = vec![0xAB; op_size];
736    let mut durations = Vec::new();
737    let fd = file.as_raw_fd();
738    for (i, offset) in offsets.iter().enumerate() {
739        storage_trace::duration!("benchmark", "pwrite", "op_number" => i, "offset" => *offset);
740        let timer = OperationTimer::start();
741        let result =
742            unsafe { libc::pwrite(fd, data.as_ptr() as *const libc::c_void, data.len(), *offset) };
743        let fsync_result = unsafe { libc::fsync(fd) };
744        durations.push(timer.stop());
745        assert_eq!(result, op_size as isize);
746        assert_eq!(fsync_result, 0);
747    }
748    durations
749}
750
751#[cfg(test)]
752mod tests {
753    use super::*;
754    use crate::testing::TestFilesystem;
755
756    const OP_SIZE: usize = 8;
757    const OP_COUNT: usize = 2;
758
759    async fn check_benchmark<T>(benchmark: T, op_count: usize, clear_cache_count: u64)
760    where
761        T: Benchmark<TestFilesystem>,
762    {
763        let mut test_fs = Box::new(TestFilesystem::new());
764        let results = benchmark.run(test_fs.as_mut()).await;
765
766        assert_eq!(results.len(), op_count);
767        assert_eq!(test_fs.clear_cache_count().await, clear_cache_count);
768        test_fs.shutdown().await;
769    }
770
771    #[fuchsia::test]
772    async fn read_sequential_cold_test() {
773        check_benchmark(
774            ReadSequentialCold::new(OP_SIZE, OP_COUNT),
775            OP_COUNT,
776            /*clear_cache_count=*/ 1,
777        )
778        .await;
779    }
780
781    #[fuchsia::test]
782    async fn read_sequential_warm_test() {
783        check_benchmark(
784            ReadSequentialWarm::new(OP_SIZE, OP_COUNT),
785            OP_COUNT,
786            /*clear_cache_count=*/ 0,
787        )
788        .await;
789    }
790
791    #[fuchsia::test]
792    async fn read_random_cold_test() {
793        check_benchmark(
794            ReadRandomCold::new(OP_SIZE, OP_COUNT),
795            OP_COUNT,
796            /*clear_cache_count=*/ 1,
797        )
798        .await;
799    }
800
801    #[fuchsia::test]
802    async fn read_sparse_cold_test() {
803        check_benchmark(
804            ReadSparseCold::new(OP_SIZE, OP_COUNT),
805            OP_COUNT,
806            /*clear_cache_count=*/ 1,
807        )
808        .await;
809    }
810
811    #[fuchsia::test]
812    async fn read_random_warm_test() {
813        check_benchmark(
814            ReadRandomWarm::new(OP_SIZE, OP_COUNT),
815            OP_COUNT,
816            /*clear_cache_count=*/ 0,
817        )
818        .await;
819    }
820
821    #[fuchsia::test]
822    async fn write_sequential_cold_test() {
823        check_benchmark(
824            WriteSequentialCold::new(OP_SIZE, OP_COUNT),
825            OP_COUNT,
826            /*clear_cache_count=*/ 0,
827        )
828        .await;
829    }
830
831    #[fuchsia::test]
832    async fn write_sequential_warm_test() {
833        check_benchmark(
834            WriteSequentialWarm::new(OP_SIZE, OP_COUNT),
835            OP_COUNT,
836            /*clear_cache_count=*/ 0,
837        )
838        .await;
839    }
840
841    #[fuchsia::test]
842    async fn write_random_cold_test() {
843        check_benchmark(
844            WriteRandomCold::new(OP_SIZE, OP_COUNT),
845            OP_COUNT,
846            /*clear_cache_count=*/ 0,
847        )
848        .await;
849    }
850
851    #[fuchsia::test]
852    async fn write_random_warm_test() {
853        check_benchmark(
854            WriteRandomWarm::new(OP_SIZE, OP_COUNT),
855            OP_COUNT,
856            /*clear_cache_count=*/ 0,
857        )
858        .await;
859    }
860
861    #[fuchsia::test]
862    async fn write_sequential_fsync_cold_test() {
863        check_benchmark(
864            WriteSequentialFsyncCold::new(OP_SIZE, OP_COUNT),
865            OP_COUNT,
866            /*clear_cache_count=*/ 0,
867        )
868        .await;
869    }
870
871    #[fuchsia::test]
872    async fn write_sequential_fsync_warm_test() {
873        check_benchmark(
874            WriteSequentialFsyncWarm::new(OP_SIZE, OP_COUNT),
875            OP_COUNT,
876            /*clear_cache_count=*/ 0,
877        )
878        .await;
879    }
880
881    #[fuchsia::test]
882    async fn write_random_fsync_cold_test() {
883        check_benchmark(
884            WriteRandomFsyncCold::new(OP_SIZE, OP_COUNT),
885            OP_COUNT,
886            /*clear_cache_count=*/ 0,
887        )
888        .await;
889    }
890
891    #[fuchsia::test]
892    async fn write_random_fsync_warm_test() {
893        check_benchmark(
894            WriteRandomFsyncWarm::new(OP_SIZE, OP_COUNT),
895            OP_COUNT,
896            /*clear_cache_count=*/ 0,
897        )
898        .await;
899    }
900}