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