//
// Syd: rock-solid application kernel
// benches/sys/open_read_close.rs: open+read+close microbenchmarks
//
// Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
// Based in part upon gVisor's open_read_close_benchmark.cc which is:
//   Copyright 2020 The gVisor Authors.
//   SPDX-License-Identifier: Apache-2.0
//
// SPDX-License-Identifier: GPL-3.0

// This benchmark replicates the gVisor "open-read-close" micro-benchmark:
//   1) We create N files each with some content.
//   2) For each iteration, we randomly pick a file, open it (O_RDONLY), read
//      1 byte, and close it.

use std::{
    env,
    ffi::CString,
    fs::{self, File},
    io::Write,
    path::{Path, PathBuf},
    time::SystemTime,
};

use brunch::{benches, Bench};
use libc::{close, open, read, O_RDONLY};
use nix::unistd::unlink;

/// Simple XorShift32 RNG to replace the usage of `rand_r`.
struct XorShift32 {
    state: u32,
}

impl XorShift32 {
    fn new(seed: u32) -> Self {
        Self { state: seed }
    }

    fn next_u32(&mut self) -> u32 {
        let mut x = self.state;
        x ^= x << 13;
        x ^= x >> 17;
        x ^= x << 5;
        self.state = x;
        x
    }
}

/// Create `count` files, each containing "some content".
fn create_files_with_content(count: usize) -> (PathBuf, Vec<PathBuf>) {
    // Create a unique directory under /tmp.
    let mut dir = env::temp_dir();
    let unique = format!(
        "open_read_close_bench_{}_{}",
        count,
        SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    );
    dir.push(unique);
    fs::create_dir_all(&dir).unwrap_or_else(|_| panic!("Failed to create directory: {:?}", &dir));

    let mut paths = Vec::with_capacity(count);
    for i in 0..count {
        let path = dir.join(format!("file_{}", i));
        let mut file =
            File::create(&path).unwrap_or_else(|_| panic!("Failed to create file: {:?}", &path));
        // Write some content.
        file.write_all(b"some content")
            .unwrap_or_else(|_| panic!("Failed to write content: {:?}", &path));
        paths.push(path);
    }
    (dir, paths)
}

/// Open a random file in O_RDONLY, read 1 byte, and close it.
fn open_read_close(files: &[PathBuf], rng: &mut XorShift32) {
    let chosen_idx = (rng.next_u32() as usize) % files.len();
    let c_path = CString::new(files[chosen_idx].to_string_lossy().as_bytes())
        .expect("Failed to convert path to CString");

    // open()
    let fd = unsafe { open(c_path.as_ptr(), O_RDONLY) };
    if fd < 0 {
        panic!("open() failed for {:?}", files[chosen_idx]);
    }

    // read()
    let mut buf = [0u8; 1];
    let result = unsafe { read(fd, buf.as_mut_ptr() as *mut _, 1) };
    if result != 1 {
        panic!("read() failed to read 1 byte (got {})", result);
    }

    // close()
    unsafe {
        close(fd);
    }
}

/// Clean up the files and the directory.
fn cleanup_temp_dir(dir: &Path, files: &[PathBuf]) {
    for f in files {
        let _ = unlink(f);
    }
    let _ = fs::remove_dir_all(dir);
}

fn main() {
    // Following the original benchmark range: 1000 to 16384
    let file_counts = [1000, 16384];
    let mut setups = Vec::new();

    // Prepare files for each count.
    for &count in &file_counts {
        let (dir, paths) = create_files_with_content(count);
        setups.push((count, dir, paths));
    }

    benches!(
        inline:

        // BM_OpenReadClose(1000)
        Bench::new("OpenReadClose(1000)").run(|| {
            static SEED: u32 = 1;
            let mut rng = XorShift32::new(SEED);
            open_read_close(&setups[0].2, &mut rng);
        }),

        // BM_OpenReadClose(16384)
        Bench::new("OpenReadClose(16384)").run(|| {
            static SEED: u32 = 1;
            let mut rng = XorShift32::new(SEED);
            open_read_close(&setups[1].2, &mut rng);
        }),
    );

    // Cleanup.
    for (_, dir, paths) in setups {
        cleanup_temp_dir(&dir, &paths);
    }
}
