use crate::{Benchmark, CacheClearableFilesystem, Filesystem, OperationDuration, OperationTimer};
use async_trait::async_trait;
use rand::seq::SliceRandom;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng;
use std::fs::OpenOptions;
use std::io::{Seek, SeekFrom, Write};
use std::os::unix::fs::FileExt;
use std::os::unix::io::AsRawFd;
const RNG_SEED: u64 = 0xda782a0c3ce1819a;
const BLOCK_SKIP: usize = 255;
#[derive(Clone)]
pub struct ReadSequentialCold {
op_size: usize,
op_count: usize,
}
impl ReadSequentialCold {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: CacheClearableFilesystem> Benchmark<T> for ReadSequentialCold {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"ReadSequentialCold",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
write_file(&mut file, self.op_size, self.op_count);
std::mem::drop(file);
fs.clear_cache().await;
let mut file = OpenOptions::new().read(true).open(&file_path).unwrap();
read_sequential(&mut file, self.op_size, self.op_count)
}
fn name(&self) -> String {
format!("ReadSequentialCold/{}", self.op_size)
}
}
#[derive(Clone)]
pub struct ReadSequentialWarm {
op_size: usize,
op_count: usize,
}
impl ReadSequentialWarm {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: Filesystem> Benchmark<T> for ReadSequentialWarm {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"ReadSequentialWarm",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file =
OpenOptions::new().write(true).read(true).create_new(true).open(&file_path).unwrap();
write_file(&mut file, self.op_size, self.op_count);
file.seek(SeekFrom::Start(0)).unwrap();
read_sequential(&mut file, self.op_size, self.op_count)
}
fn name(&self) -> String {
format!("ReadSequentialWarm/{}", self.op_size)
}
}
#[derive(Clone)]
pub struct ReadRandomCold {
op_size: usize,
op_count: usize,
}
impl ReadRandomCold {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: CacheClearableFilesystem> Benchmark<T> for ReadRandomCold {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"ReadRandomCold",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
write_file(&mut file, self.op_size, self.op_count);
std::mem::drop(file);
fs.clear_cache().await;
let mut rng = XorShiftRng::seed_from_u64(RNG_SEED);
let mut file = OpenOptions::new().read(true).open(&file_path).unwrap();
read_random(&mut file, self.op_size, self.op_count, &mut rng)
}
fn name(&self) -> String {
format!("ReadRandomCold/{}", self.op_size)
}
}
#[derive(Clone)]
pub struct ReadSparseCold {
op_size: usize,
op_count: usize,
}
impl ReadSparseCold {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: CacheClearableFilesystem> Benchmark<T> for ReadSparseCold {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"ReadSparseCold",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
write_sparse_file(&mut file, self.op_size, self.op_count, BLOCK_SKIP);
std::mem::drop(file);
fs.clear_cache().await;
let mut file = OpenOptions::new().read(true).open(&file_path).unwrap();
read_sparse(&mut file, self.op_size, self.op_count, BLOCK_SKIP)
}
fn name(&self) -> String {
format!("ReadSparseCold/{}", self.op_size)
}
}
#[derive(Clone)]
pub struct ReadRandomWarm {
op_size: usize,
op_count: usize,
}
impl ReadRandomWarm {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: Filesystem> Benchmark<T> for ReadRandomWarm {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"ReadRandomWarm",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file =
OpenOptions::new().write(true).read(true).create_new(true).open(&file_path).unwrap();
write_file(&mut file, self.op_size, self.op_count);
let mut rng = XorShiftRng::seed_from_u64(RNG_SEED);
read_random(&mut file, self.op_size, self.op_count, &mut rng)
}
fn name(&self) -> String {
format!("ReadRandomWarm/{}", self.op_size)
}
}
#[derive(Clone)]
pub struct WriteSequentialCold {
op_size: usize,
op_count: usize,
}
impl WriteSequentialCold {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: Filesystem> Benchmark<T> for WriteSequentialCold {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"WriteSequentialCold",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
write_sequential(&mut file, self.op_size, self.op_count)
}
fn name(&self) -> String {
format!("WriteSequentialCold/{}", self.op_size)
}
}
#[derive(Clone)]
pub struct WriteSequentialWarm {
op_size: usize,
op_count: usize,
}
impl WriteSequentialWarm {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: Filesystem> Benchmark<T> for WriteSequentialWarm {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"WriteSequentialWarm",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
write_file(&mut file, self.op_size, self.op_count);
file.seek(SeekFrom::Start(0)).unwrap();
write_sequential(&mut file, self.op_size, self.op_count)
}
fn name(&self) -> String {
format!("WriteSequentialWarm/{}", self.op_size)
}
}
#[derive(Clone)]
pub struct WriteRandomCold {
op_size: usize,
op_count: usize,
}
impl WriteRandomCold {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: Filesystem> Benchmark<T> for WriteRandomCold {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"WriteRandomCold",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
let mut rng = XorShiftRng::seed_from_u64(RNG_SEED);
write_random(&mut file, self.op_size, self.op_count, &mut rng)
}
fn name(&self) -> String {
format!("WriteRandomCold/{}", self.op_size)
}
}
#[derive(Clone)]
pub struct WriteRandomWarm {
op_size: usize,
op_count: usize,
}
impl WriteRandomWarm {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: Filesystem> Benchmark<T> for WriteRandomWarm {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"WriteRandomWarm",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
write_sequential(&mut file, self.op_size, self.op_count);
let mut rng = XorShiftRng::seed_from_u64(RNG_SEED);
write_random(&mut file, self.op_size, self.op_count, &mut rng)
}
fn name(&self) -> String {
format!("WriteRandomWarm/{}", self.op_size)
}
}
#[derive(Clone)]
pub struct WriteSequentialFsyncCold {
op_size: usize,
op_count: usize,
}
impl WriteSequentialFsyncCold {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: Filesystem> Benchmark<T> for WriteSequentialFsyncCold {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"WriteSequentialFsyncCold",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
write_sequential_fsync(&mut file, self.op_size, self.op_count)
}
fn name(&self) -> String {
format!("WriteSequentialFsyncCold/{}", self.op_size)
}
}
#[derive(Clone)]
pub struct WriteSequentialFsyncWarm {
op_size: usize,
op_count: usize,
}
impl WriteSequentialFsyncWarm {
pub fn new(op_size: usize, op_count: usize) -> Self {
Self { op_size, op_count }
}
}
#[async_trait]
impl<T: Filesystem> Benchmark<T> for WriteSequentialFsyncWarm {
async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
storage_trace::duration!(
c"benchmark",
c"WriteSequentialFsyncWarm",
"op_size" => self.op_size,
"op_count" => self.op_count
);
let file_path = fs.benchmark_dir().join("file");
let mut file = OpenOptions::new().write(true).create_new(true).open(&file_path).unwrap();
write_sequential(&mut file, self.op_size, self.op_count);
assert_eq!(unsafe { libc::fsync(file.as_raw_fd()) }, 0);
file.seek(SeekFrom::Start(0)).unwrap();
write_sequential_fsync(&mut file, self.op_size, self.op_count)
}
fn name(&self) -> String {
format!("WriteSequentialFsyncWarm/{}", self.op_size)
}
}
fn write_file<F: Write + FileExt>(file: &mut F, op_size: usize, op_count: usize) {
write_sparse_file(file, op_size, op_count, 0);
}
fn write_sparse_file<F: Write + FileExt>(
file: &mut F,
op_size: usize,
op_count: usize,
block_skip: usize,
) {
let data = vec![0xAB; op_size];
let mut offset: u64 = 0;
for _ in 0..op_count {
assert_eq!(file.write_at(&data, offset).unwrap(), op_size);
offset += (op_size * (block_skip + 1)) as u64;
}
}
fn read_sequential<F: AsRawFd>(
file: &mut F,
op_size: usize,
op_count: usize,
) -> Vec<OperationDuration> {
let mut data = vec![0; op_size];
let mut durations = Vec::new();
let fd = file.as_raw_fd();
for i in 0..op_count {
storage_trace::duration!(c"benchmark", c"read", "op_number" => i);
let timer = OperationTimer::start();
let result = unsafe { libc::read(fd, data.as_mut_ptr() as *mut libc::c_void, data.len()) };
durations.push(timer.stop());
assert_eq!(result, op_size as isize);
}
durations
}
fn read_sparse<F: AsRawFd>(
file: &mut F,
op_size: usize,
op_count: usize,
block_skip: usize,
) -> Vec<OperationDuration> {
let mut data = vec![0; op_size];
let mut durations = Vec::new();
let fd = file.as_raw_fd();
let sparse_offset = ((1 + block_skip) * op_size) as i64;
for i in 0..op_count as i64 {
storage_trace::duration!(c"benchmark", c"pread", "op_number" => i);
let timer = OperationTimer::start();
let result = unsafe {
libc::pread(fd, data.as_mut_ptr() as *mut libc::c_void, data.len(), i * sparse_offset)
};
durations.push(timer.stop());
assert_eq!(result, op_size as isize);
}
durations
}
fn write_sequential<F: AsRawFd>(
file: &mut F,
op_size: usize,
op_count: usize,
) -> Vec<OperationDuration> {
let data = vec![0xAB; op_size];
let mut durations = Vec::new();
let fd = file.as_raw_fd();
for i in 0..op_count {
storage_trace::duration!(c"benchmark", c"write", "op_number" => i);
let timer = OperationTimer::start();
let result = unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, data.len()) };
durations.push(timer.stop());
assert_eq!(result, op_size as isize);
}
durations
}
fn write_sequential_fsync<F: AsRawFd>(
file: &mut F,
op_size: usize,
op_count: usize,
) -> Vec<OperationDuration> {
let data = vec![0xAB; op_size];
let mut durations = Vec::new();
let fd = file.as_raw_fd();
for i in 0..op_count {
storage_trace::duration!(c"benchmark", c"write", "op_number" => i);
let timer = OperationTimer::start();
let write_result =
unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, data.len()) };
let fsync_result = unsafe { libc::fsync(fd) };
durations.push(timer.stop());
assert_eq!(write_result, op_size as isize);
assert_eq!(fsync_result, 0);
}
durations
}
fn create_random_offsets<R: Rng>(op_size: usize, op_count: usize, rng: &mut R) -> Vec<libc::off_t> {
let op_count = op_count as libc::off_t;
let op_size = op_size as libc::off_t;
let mut offsets: Vec<libc::off_t> = (0..op_count).map(|offset| offset * op_size).collect();
offsets.shuffle(rng);
offsets
}
fn read_random<F: AsRawFd, R: Rng>(
file: &mut F,
op_size: usize,
op_count: usize,
rng: &mut R,
) -> Vec<OperationDuration> {
let offsets = create_random_offsets(op_size, op_count, rng);
let mut data = vec![0xAB; op_size];
let mut durations = Vec::new();
let fd = file.as_raw_fd();
for (i, offset) in offsets.iter().enumerate() {
storage_trace::duration!(c"benchmark", c"pread", "op_number" => i, "offset" => *offset);
let timer = OperationTimer::start();
let result =
unsafe { libc::pread(fd, data.as_mut_ptr() as *mut libc::c_void, data.len(), *offset) };
durations.push(timer.stop());
assert_eq!(result, op_size as isize);
}
durations
}
fn write_random<F: AsRawFd, R: Rng>(
file: &mut F,
op_size: usize,
op_count: usize,
rng: &mut R,
) -> Vec<OperationDuration> {
let offsets = create_random_offsets(op_size, op_count, rng);
let data = vec![0xAB; op_size];
let mut durations = Vec::new();
let fd = file.as_raw_fd();
for (i, offset) in offsets.iter().enumerate() {
storage_trace::duration!(c"benchmark", c"pwrite", "op_number" => i, "offset" => *offset);
let timer = OperationTimer::start();
let result =
unsafe { libc::pwrite(fd, data.as_ptr() as *const libc::c_void, data.len(), *offset) };
durations.push(timer.stop());
assert_eq!(result, op_size as isize);
}
durations
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::TestFilesystem;
const OP_SIZE: usize = 8;
const OP_COUNT: usize = 2;
async fn check_benchmark<T>(benchmark: T, op_count: usize, clear_cache_count: u64)
where
T: Benchmark<TestFilesystem>,
{
let mut test_fs = Box::new(TestFilesystem::new());
let results = benchmark.run(test_fs.as_mut()).await;
assert_eq!(results.len(), op_count);
assert_eq!(test_fs.clear_cache_count().await, clear_cache_count);
test_fs.shutdown().await;
}
#[fuchsia::test]
async fn read_sequential_cold_test() {
check_benchmark(
ReadSequentialCold::new(OP_SIZE, OP_COUNT),
OP_COUNT,
1,
)
.await;
}
#[fuchsia::test]
async fn read_sequential_warm_test() {
check_benchmark(
ReadSequentialWarm::new(OP_SIZE, OP_COUNT),
OP_COUNT,
0,
)
.await;
}
#[fuchsia::test]
async fn read_random_cold_test() {
check_benchmark(
ReadRandomCold::new(OP_SIZE, OP_COUNT),
OP_COUNT,
1,
)
.await;
}
#[fuchsia::test]
async fn read_sparse_cold_test() {
check_benchmark(
ReadSparseCold::new(OP_SIZE, OP_COUNT),
OP_COUNT,
1,
)
.await;
}
#[fuchsia::test]
async fn read_random_warm_test() {
check_benchmark(
ReadRandomWarm::new(OP_SIZE, OP_COUNT),
OP_COUNT,
0,
)
.await;
}
#[fuchsia::test]
async fn write_sequential_cold_test() {
check_benchmark(
WriteSequentialCold::new(OP_SIZE, OP_COUNT),
OP_COUNT,
0,
)
.await;
}
#[fuchsia::test]
async fn write_sequential_warm_test() {
check_benchmark(
WriteSequentialWarm::new(OP_SIZE, OP_COUNT),
OP_COUNT,
0,
)
.await;
}
#[fuchsia::test]
async fn write_random_cold_test() {
check_benchmark(
WriteRandomCold::new(OP_SIZE, OP_COUNT),
OP_COUNT,
0,
)
.await;
}
#[fuchsia::test]
async fn write_random_warm_test() {
check_benchmark(
WriteRandomWarm::new(OP_SIZE, OP_COUNT),
OP_COUNT,
0,
)
.await;
}
#[fuchsia::test]
async fn write_sequential_fsync_cold_test() {
check_benchmark(
WriteSequentialFsyncCold::new(OP_SIZE, OP_COUNT),
OP_COUNT,
0,
)
.await;
}
#[fuchsia::test]
async fn write_sequential_fsync_warm_test() {
check_benchmark(
WriteSequentialFsyncWarm::new(OP_SIZE, OP_COUNT),
OP_COUNT,
0,
)
.await;
}
}