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

// A micro-benchmark that approximates the gVisor stat micro-benchmark.
// It creates a nested directory tree up to a given `depth`, places one
// file at the bottom, and calls `stat()` on that file repeatedly.
// See: https://raw.githubusercontent.com/google/gvisor/refs/heads/master/test/perf/linux/stat_benchmark.cc

use std::{
    env,
    fs::{self, File},
    path::PathBuf,
    time::SystemTime,
};

use brunch::{benches, Bench};
use nix::{errno::Errno, sys::stat::stat};

/// Create a nested directory structure up to `depth` layers, put one
/// file in the final directory, and return `(top-level-dir,
/// file-path)`.
fn setup_tree(depth: usize) -> (PathBuf, PathBuf) {
    // Create a unique top-level directory in /tmp (or equivalent).
    let mut dir = env::temp_dir();
    let unique = format!(
        "syd_stat_bench_depth_{}_{}",
        depth,
        SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    );
    dir.push(unique);
    fs::create_dir_all(&dir).expect("Failed to create top-level directory");

    // Build subdirectories.
    let mut sub = dir.clone();
    for d in 0..depth {
        sub.push(d.to_string());
        fs::create_dir_all(&sub).expect("Failed to create subdirectory");
    }

    // Finally, create our file at the deepest level.
    sub.push("benchmark_file");
    File::create(&sub).expect("Failed to create benchmark file");

    // Return both the top-level directory (for cleanup) and the final file path.
    let file_path = sub.clone();
    sub.pop();
    (dir, file_path)
}

/// Perform one `stat()` call on the given file path.
fn bench_stat(file_path: &PathBuf) -> Result<(), Errno> {
    stat(file_path).map(drop)
}

fn main() {
    // Create a few sets of directories to benchmark different depths.
    let depths = [1, 2, 10, 100];

    // We'll store (depth, top_dir, file_path) for each setup so we can
    // clean up afterwards.
    let mut setups = Vec::new();
    for &d in &depths {
        let (dir, file) = setup_tree(d);
        setups.push((d, dir, file));
    }

    // Define our benches inline. Each is a separate benchmark that
    // measures calling `stat()` on the file at a given depth.
    benches!(
        inline:

        Bench::new("Stat depth=1").run(|| {
            bench_stat(&setups[0].2).unwrap();
        }),

        Bench::new("Stat depth=2").run(|| {
            bench_stat(&setups[1].2).unwrap();
        }),

        Bench::new("Stat depth=10").run(|| {
            bench_stat(&setups[2].2).unwrap();
        }),

        Bench::new("Stat depth=100").run(|| {
            bench_stat(&setups[3].2).unwrap();
        }),
    );

    // Cleanup: remove all created directories (and files).
    // You can comment this out if you want to inspect them after the benchmark.
    for (_, dir, _) in setups {
        let _ = fs::remove_dir_all(dir);
    }
}
