diff --git a/blackheap/Cargo.toml b/blackheap/Cargo.toml index 7d436a6..56324b5 100644 --- a/blackheap/Cargo.toml +++ b/blackheap/Cargo.toml @@ -16,7 +16,9 @@ blackheap-core = { path = "../blackheap-core" } clap = { version = "4.5", features = ["derive"] } human-panic = "1.2" libc = "0.2" +serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" +toml = "0.8" tracing = "0.1" tracing-subscriber = "0.3" uuid = { version = "1.7", features = ["v4", "fast-rng"] } diff --git a/blackheap/src/assets/mod.rs b/blackheap/src/assets/mod.rs new file mode 100644 index 0000000..efd2180 --- /dev/null +++ b/blackheap/src/assets/mod.rs @@ -0,0 +1 @@ +mod progress; diff --git a/blackheap/src/assets/progress.rs b/blackheap/src/assets/progress.rs new file mode 100644 index 0000000..897a645 --- /dev/null +++ b/blackheap/src/assets/progress.rs @@ -0,0 +1,109 @@ +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fs}; +use thiserror::Error; + +use crate::{BenchmarkScenario, Benchmark}; + +const VERSION_NUMBER: u32 = 1; + +#[derive(Error, Debug)] +pub enum ProgressError { + #[error("Serialization failed with: {0}")] + SerializeError(#[from] toml::ser::Error), + + #[error("Deserialization failed with: {0}")] + DeserializeError(#[from] toml::de::Error), + + + #[error("IO failed with: {0}")] + IOError(#[from] std::io::Error), +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +struct Meta { + version: u32, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] +enum Operation { + Read, + Write, +} + +impl ToString for Operation { + fn to_string(&self) -> String { + match self { + Operation::Read => "read".to_string(), + Operation::Write => "write".to_string(), + } + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +struct BenchmarkStatus { + done: bool, + #[serde(rename = "access-sizes-done")] + access_sizes_done: Vec, + #[serde(rename = "access-sizes-missing")] + access_sizes_missing: Vec, +} + + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +struct BenchmarkProgressToml { + meta: Meta, + benchmarks: HashMap>, +} + + +impl BenchmarkProgressToml { + pub fn new_from_benchmarks(benchmarks: &[Benchmark], access_sizes: &[u32]) -> Self { + let mut benchmarks_map: HashMap> = HashMap::new(); + + for benchmark in benchmarks { + let operation = if benchmark.config.is_read_operation { + Operation::Read + } else { + Operation::Write + }; + + let status = BenchmarkStatus { + done: false, + access_sizes_done: vec![], + access_sizes_missing: access_sizes.to_vec(), + }; + + let scenario_map = benchmarks_map.entry(benchmark.scenario).or_insert_with(HashMap::new); + scenario_map.insert(operation, status); + } + + BenchmarkProgressToml { + meta: Meta { version: VERSION_NUMBER }, + benchmarks: benchmarks_map, + } + } + + pub fn get_missing_access_sizes(&self, b: &Benchmark) -> Option<&[u32]> { + let operation = match b.config.is_read_operation { + True => Operation::Read, + False => Operation::Write, + }; + + self.benchmarks.get(&b.scenario) + .and_then(|scenario_map| scenario_map.get(&operation)) + .map(|status| status.access_sizes_missing.as_slice()) + } + + + pub fn to_file(&self, path: &str) -> Result<(), ProgressError> { + let toml_str = toml::to_string(&self)?; + fs::write(path, &toml_str)?; + Ok(()) + } + + pub fn from_file(path: &str) -> Result { + let toml_str = fs::read_to_string(path)?; + let toml: BenchmarkProgressToml = toml::from_str(&toml_str)?; + Ok(toml) + } +} diff --git a/blackheap/src/cli.rs b/blackheap/src/cli.rs index 8fe15b8..282383e 100644 --- a/blackheap/src/cli.rs +++ b/blackheap/src/cli.rs @@ -1,10 +1,13 @@ -use std::{path::{PathBuf, Path}, io, fs::{self, File}}; +use std::{ + fs::{self, File}, + io, + path::{Path, PathBuf}, +}; use clap::{Parser, ValueEnum}; use thiserror::Error; use uuid::Uuid; - #[derive(Error, Debug)] pub enum CliError { #[error("Roor privileges are required for dropping caches")] @@ -41,7 +44,7 @@ fn validate_output_directory(dir: &Path) -> Result<(), CliError> { - the path exists but is not a directory - the path exists, is a directory but not writable */ - + if !dir.exists() { fs::create_dir_all(dir).map_err(|_| CliError::CannotCreateDirectory(dir.to_path_buf()))?; } @@ -61,7 +64,8 @@ fn validate_output_directory(dir: &Path) -> Result<(), CliError> { } test_file }; - File::create(&test_file).and_then(|_| fs::remove_file(&test_file)) + File::create(&test_file) + .and_then(|_| fs::remove_file(&test_file)) .map_err(|_| CliError::DirectoryNotWritable(dir.to_path_buf()))?; Ok(()) @@ -69,7 +73,7 @@ fn validate_output_directory(dir: &Path) -> Result<(), CliError> { fn validate_benchmark_file(file: &Path) -> Result<(), CliError> { /* - The benchmark file is invalid if + The benchmark file is invalid if - it doesnt exist and cannot be created - it cannot be removed */ @@ -118,6 +122,4 @@ pub struct Cli { /// Drop caches (requires root) #[clap(long)] drop_caches: bool, - } - diff --git a/blackheap/src/main.rs b/blackheap/src/main.rs index 1250d07..ea963b4 100644 --- a/blackheap/src/main.rs +++ b/blackheap/src/main.rs @@ -1,21 +1,148 @@ use crate::cli::Cli; -use tracing::info; - +use blackheap_benchmarker::{AccessPattern, BenchmarkConfig}; use clap::Parser; +use serde::{Serialize, Deserialize}; +use tracing::{debug, error, info}; +mod assets; mod cli; +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum BenchmarkScenario { + RandomUncached, + SameOffset, +} + +impl ToString for BenchmarkScenario { + fn to_string(&self) -> String { + match self { + BenchmarkScenario::SameOffset => "SameOffset".to_string(), + BenchmarkScenario::RandomUncached => "RandomUncached".to_string(), + } + } +} + +#[derive(Debug, Clone)] +struct Benchmark { + scenario: BenchmarkScenario, + config: BenchmarkConfig, +} + +impl Benchmark { + pub fn get_all_benchmarks( + root: bool, + file_path: &str, + ) -> Vec { + vec![ + Self::new_random_uncached_read(file_path, root), + Self::new_random_uncached_write(file_path, root), + Self::new_same_offset_read(file_path), + Self::new_same_offset_write(file_path), + ] + } + + pub fn new_random_uncached_read(file_path: &str, root: bool) -> Self { + Benchmark { + scenario: BenchmarkScenario::RandomUncached, + config: BenchmarkConfig { + filepath: file_path.to_string(), + memory_buffer_in_bytes: 4 * 1024 * 1024 * 1024, + file_size_in_bytes: 25 * 1024 * 1024 * 1024, + access_size_in_bytes: 4 * 1024, /* any random value */ + number_of_io_op_tests: 1000, + access_pattern_in_memory: AccessPattern::Random, + access_pattern_in_file: AccessPattern::Random, + is_read_operation: true, + prepare_file_size: true, + drop_cache_first: root, + do_reread: false, + restrict_free_ram_to: None, + }, + } + } + + pub fn new_random_uncached_write(file_path: &str, root: bool) -> Self { + Benchmark { + scenario: BenchmarkScenario::RandomUncached, + config: { + let mut config = Self::new_random_uncached_read(file_path, root).config; + config.is_read_operation = false; + config + }, + } + } + + pub fn new_same_offset_read(file_path: &str) -> Self { + Benchmark { + scenario: BenchmarkScenario::SameOffset, + config: BenchmarkConfig { + filepath: file_path.to_string(), + memory_buffer_in_bytes: 4 * 1024 * 1024 * 1024, + file_size_in_bytes: 25 * 1024 * 1024 * 1024, + access_size_in_bytes: 4 * 1024, /* any random value */ + number_of_io_op_tests: 1000, + access_pattern_in_memory: AccessPattern::Const, + access_pattern_in_file: AccessPattern::Const, + is_read_operation: true, + prepare_file_size: true, + drop_cache_first: false, + do_reread: true, + restrict_free_ram_to: None, + }, + } + } + + pub fn new_same_offset_write(file_path: &str) -> Self { + Benchmark { + scenario: BenchmarkScenario::SameOffset, + config: { + let mut config = Self::new_same_offset_read(file_path).config; + config.is_read_operation = false; + config + }, + } + } +} fn main() { + /* Init boilerplate */ human_panic::setup_panic!(); tracing_subscriber::fmt::init(); + /* CLI parsing */ info!("Parsing and validating CLI"); let cli = Cli::parse(); + debug!("{:?}", &cli); if let Err(e) = cli::validate_cli(&cli) { - println!("{:?}", e); + error!("{:?}", e); + std::process::exit(1); } - println!("{:?}", cli); + /* Create folder / Load old data */ + + /* + Old Logic: + - Create output folder + - dump static files + - Create a vector of all performance benchmarks + - For all benchmarks: + - if not `analyze_only` run and save the benchmark + - run the analysis + - dump all to file + - model.json + - iofs.csv + */ + + /* + New Logic: + - try loading previous data => into Option<> + - if not, create progress file (in toml) + - run all benchmarks one by one + - update afterwards + - start analysis + - TODO if the plotting libraries arent good enough dump a python script in there + - lin reg should still be done in here + - Maybe do that in general? + */ }