commit 5d5f15a7da94573469a7f2fbaf5f50df2bd4d7c7 Author: PatchRequest <47796889+PatchRequest@users.noreply.github.com> Date: Thu Jun 4 12:12:32 2026 +0200 Initial implementation of busywork library Pattern-breaking sleep replacement that executes real, varied work on every call. Randomized task selection across 7 categories (compute, memory, filesystem, registry, winapi, network, crypto) with intensity levels and jitter. Zero time objects in the library binary. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6a20b95 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,394 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "bitflags" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "busywork" +version = "0.1.0" +dependencies = [ + "bitflags", + "flate2", + "md-5", + "rand", + "sha2", + "windows", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6addb23 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "busywork" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = "0.8" +bitflags = "2" +sha2 = { version = "0.10", optional = true } +md-5 = { version = "0.10", optional = true } +flate2 = { version = "1", optional = true } + +[dependencies.windows] +version = "0.58" +optional = true + +[features] +default = [ + "cat-compute", + "cat-memory", + "cat-filesystem", + "cat-registry", + "cat-winapi", + "cat-network", + "cat-crypto", +] +cat-compute = ["dep:sha2", "dep:md-5", "dep:flate2"] +cat-memory = [] +cat-filesystem = [] +cat-registry = [ + "dep:windows", + "windows/Win32_System_Registry", + "windows/Win32_Foundation", +] +cat-winapi = [ + "dep:windows", + "windows/Win32_UI_WindowsAndMessaging", + "windows/Win32_System_Diagnostics_ToolHelp", + "windows/Win32_System_SystemInformation", + "windows/Win32_System_DataExchange", + "windows/Win32_Foundation", + "windows/Win32_System_Threading", +] +cat-network = [] +cat-crypto = [ + "dep:windows", + "windows/Win32_Security_Cryptography", + "windows/Win32_Foundation", +] diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..0322c11 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,41 @@ +use crate::categories::Categories; +use crate::dispatch; +use crate::intensity::Intensity; + +pub struct BusyWork { + intensity: Intensity, + allow: Categories, + deny: Categories, + jitter: bool, +} + +impl BusyWork { + pub fn new(intensity: Intensity) -> Self { + Self { + intensity, + allow: Categories::all(), + deny: Categories::empty(), + jitter: true, + } + } + + pub fn allow(mut self, cats: Categories) -> Self { + self.allow = cats; + self + } + + pub fn deny(mut self, cats: Categories) -> Self { + self.deny = cats; + self + } + + pub fn jitter(mut self, enabled: bool) -> Self { + self.jitter = enabled; + self + } + + pub fn run(&self) { + let effective = (self.allow & Categories::available()) & !self.deny; + dispatch::execute(self.intensity, effective, self.jitter); + } +} diff --git a/src/categories.rs b/src/categories.rs new file mode 100644 index 0000000..ddf2f4a --- /dev/null +++ b/src/categories.rs @@ -0,0 +1,49 @@ +use bitflags::bitflags; + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct Categories: u32 { + const COMPUTE = 0b000_0001; + const MEMORY = 0b000_0010; + const FILESYSTEM = 0b000_0100; + const REGISTRY = 0b000_1000; + const WINAPI = 0b001_0000; + const NETWORK = 0b010_0000; + const CRYPTO = 0b100_0000; + } +} + +impl Categories { + pub fn available() -> Self { + let mut cats = Self::empty(); + #[cfg(feature = "cat-compute")] + { + cats |= Self::COMPUTE; + } + #[cfg(feature = "cat-memory")] + { + cats |= Self::MEMORY; + } + #[cfg(feature = "cat-filesystem")] + { + cats |= Self::FILESYSTEM; + } + #[cfg(feature = "cat-registry")] + { + cats |= Self::REGISTRY; + } + #[cfg(feature = "cat-winapi")] + { + cats |= Self::WINAPI; + } + #[cfg(feature = "cat-network")] + { + cats |= Self::NETWORK; + } + #[cfg(feature = "cat-crypto")] + { + cats |= Self::CRYPTO; + } + cats + } +} diff --git a/src/dispatch.rs b/src/dispatch.rs new file mode 100644 index 0000000..d3fa4f4 --- /dev/null +++ b/src/dispatch.rs @@ -0,0 +1,47 @@ +use crate::categories::Categories; +use crate::intensity::Intensity; +use crate::jitter; +use crate::tasks::{self, TaskParams}; +use rand::seq::SliceRandom; + +pub fn execute(intensity: Intensity, effective: Categories, jitter_enabled: bool) { + let mut rng = rand::thread_rng(); + let all = tasks::all_tasks(); + let eligible: Vec<_> = all + .iter() + .filter(|t| effective.contains(t.category)) + .collect(); + + if eligible.is_empty() { + return; + } + + let base = intensity.base_params(); + let count = if jitter_enabled { + jitter::apply(base.task_count, &mut rng) + } else { + base.task_count + }; + + for _ in 0..count { + let task = eligible.choose(&mut rng).unwrap(); + let params = TaskParams { + iterations: if jitter_enabled { + jitter::apply(base.iteration_count, &mut rng) + } else { + base.iteration_count + }, + buffer_size: if jitter_enabled { + jitter::apply(base.buffer_size, &mut rng) + } else { + base.buffer_size + }, + call_depth: if jitter_enabled { + jitter::apply(base.call_depth, &mut rng) + } else { + base.call_depth + }, + }; + (task.func)(¶ms, &mut rng); + } +} diff --git a/src/intensity.rs b/src/intensity.rs new file mode 100644 index 0000000..18f92ef --- /dev/null +++ b/src/intensity.rs @@ -0,0 +1,45 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Intensity { + Low, + Medium, + High, + Ultra, +} + +pub(crate) struct IntensityParams { + pub task_count: usize, + pub iteration_count: usize, + pub buffer_size: usize, + pub call_depth: usize, +} + +impl Intensity { + pub(crate) fn base_params(&self) -> IntensityParams { + match self { + Intensity::Low => IntensityParams { + task_count: 2, + iteration_count: 50, + buffer_size: 1024, + call_depth: 2, + }, + Intensity::Medium => IntensityParams { + task_count: 5, + iteration_count: 500, + buffer_size: 16384, + call_depth: 4, + }, + Intensity::High => IntensityParams { + task_count: 10, + iteration_count: 5000, + buffer_size: 262144, + call_depth: 8, + }, + Intensity::Ultra => IntensityParams { + task_count: 20, + iteration_count: 50000, + buffer_size: 1048576, + call_depth: 16, + }, + } + } +} diff --git a/src/jitter.rs b/src/jitter.rs new file mode 100644 index 0000000..78661cf --- /dev/null +++ b/src/jitter.rs @@ -0,0 +1,6 @@ +use rand::Rng; + +pub fn apply(base: usize, rng: &mut impl Rng) -> usize { + let factor: f64 = rng.gen_range(0.7..=1.3); + (base as f64 * factor).round().max(1.0) as usize +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e255b2e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,18 @@ +mod builder; +mod categories; +mod dispatch; +mod intensity; +mod jitter; +mod tasks; + +pub use builder::BusyWork; +pub use categories::Categories; +pub use intensity::Intensity; + +pub fn busywork(intensity: Intensity) { + BusyWork::new(intensity).run(); +} + +pub fn busywork_with(intensity: Intensity, categories: Categories) { + BusyWork::new(intensity).allow(categories).run(); +} diff --git a/src/tasks/compute.rs b/src/tasks/compute.rs new file mode 100644 index 0000000..da058b9 --- /dev/null +++ b/src/tasks/compute.rs @@ -0,0 +1,147 @@ +use crate::categories::Categories; +use crate::tasks::{TaskDescriptor, TaskParams}; +use flate2::write::{DeflateDecoder, DeflateEncoder}; +use flate2::Compression; +use md5::Md5; +use rand::rngs::ThreadRng; +use rand::{Rng, RngCore}; +use sha2::{Digest, Sha256}; +use std::hint::black_box; +use std::io::Write; + +pub fn register() -> Vec { + vec![ + TaskDescriptor { + name: "hash_sha256_loop", + category: Categories::COMPUTE, + func: hash_sha256_loop, + }, + TaskDescriptor { + name: "hash_md5_loop", + category: Categories::COMPUTE, + func: hash_md5_loop, + }, + TaskDescriptor { + name: "prime_sieve", + category: Categories::COMPUTE, + func: prime_sieve, + }, + TaskDescriptor { + name: "matrix_multiply", + category: Categories::COMPUTE, + func: matrix_multiply, + }, + TaskDescriptor { + name: "sort_random_arrays", + category: Categories::COMPUTE, + func: sort_random_arrays, + }, + TaskDescriptor { + name: "compress_decompress", + category: Categories::COMPUTE, + func: compress_decompress, + }, + ] +} + +fn hash_sha256_loop(params: &TaskParams, rng: &mut ThreadRng) { + let mut data = vec![0u8; 64]; + rng.fill_bytes(&mut data); + for _ in 0..params.iterations { + let mut hasher = Sha256::new(); + hasher.update(&data); + let result = hasher.finalize(); + data[..32].copy_from_slice(&result); + } + black_box(&data); +} + +fn hash_md5_loop(params: &TaskParams, rng: &mut ThreadRng) { + let mut data = vec![0u8; 64]; + rng.fill_bytes(&mut data); + for _ in 0..params.iterations { + let mut hasher = Md5::new(); + hasher.update(&data); + let result = hasher.finalize(); + data[..16].copy_from_slice(&result); + } + black_box(&data); +} + +fn prime_sieve(params: &TaskParams, _rng: &mut ThreadRng) { + let limit = params.iterations.saturating_mul(100).min(10_000_000); + if limit < 2 { + return; + } + let mut sieve = vec![true; limit]; + sieve[0] = false; + sieve[1] = false; + let sqrt_limit = (limit as f64).sqrt() as usize; + for i in 2..=sqrt_limit { + if sieve[i] { + let mut j = i * i; + while j < limit { + sieve[j] = false; + j += i; + } + } + } + let count = sieve.iter().filter(|&&b| b).count(); + black_box(count); +} + +fn matrix_multiply(params: &TaskParams, rng: &mut ThreadRng) { + let n = ((params.iterations as f64).sqrt() as usize).max(2).min(512); + let mut a = vec![0.0f64; n * n]; + let mut b = vec![0.0f64; n * n]; + for x in a.iter_mut() { + *x = rng.gen::(); + } + for x in b.iter_mut() { + *x = rng.gen::(); + } + let mut c = vec![0.0f64; n * n]; + for i in 0..n { + for k in 0..n { + let a_ik = a[i * n + k]; + for j in 0..n { + c[i * n + j] += a_ik * b[k * n + j]; + } + } + } + black_box(&c); +} + +fn sort_random_arrays(params: &TaskParams, rng: &mut ThreadRng) { + let size = (params.buffer_size / 8).max(1); + for _ in 0..params.call_depth { + let mut data: Vec = (0..size).map(|_| rng.gen()).collect(); + data.sort_unstable(); + black_box(&data); + } +} + +fn compress_decompress(params: &TaskParams, rng: &mut ThreadRng) { + let size = params.buffer_size.min(65536); + let mut data = vec![0u8; size]; + rng.fill_bytes(&mut data); + for _ in 0..params.call_depth { + let mut encoder = DeflateEncoder::new(Vec::new(), Compression::fast()); + if encoder.write_all(&data).is_err() { + continue; + } + let compressed = match encoder.finish() { + Ok(c) => c, + Err(_) => continue, + }; + let mut decoder = DeflateDecoder::new(Vec::new()); + if decoder.write_all(&compressed).is_err() { + continue; + } + let decompressed = match decoder.finish() { + Ok(d) => d, + Err(_) => continue, + }; + black_box(&decompressed); + } +} diff --git a/src/tasks/crypto.rs b/src/tasks/crypto.rs new file mode 100644 index 0000000..0a7905e --- /dev/null +++ b/src/tasks/crypto.rs @@ -0,0 +1,73 @@ +use crate::categories::Categories; +use crate::tasks::{TaskDescriptor, TaskParams}; +use rand::rngs::ThreadRng; +use rand::RngCore; +use std::hint::black_box; +use windows::Win32::Security::Cryptography::*; + +pub fn register() -> Vec { + vec![ + TaskDescriptor { + name: "bcrypt_gen_random", + category: Categories::CRYPTO, + func: bcrypt_gen_random, + }, + TaskDescriptor { + name: "bcrypt_hash", + category: Categories::CRYPTO, + func: bcrypt_hash, + }, + ] +} + +fn bcrypt_gen_random(params: &TaskParams, _rng: &mut ThreadRng) { + let size = params.buffer_size.min(65536); + let mut buffer = vec![0u8; size]; + for _ in 0..params.iterations.min(100) { + unsafe { + let _ = BCryptGenRandom( + BCRYPT_ALG_HANDLE::default(), + &mut buffer, + BCRYPT_USE_SYSTEM_PREFERRED_RNG, + ); + } + black_box(&buffer); + } +} + +fn bcrypt_hash(params: &TaskParams, rng: &mut ThreadRng) { + let data_size = params.buffer_size.min(65536); + let mut data = vec![0u8; data_size]; + rng.fill_bytes(&mut data); + + unsafe { + let mut alg = BCRYPT_ALG_HANDLE::default(); + let status = BCryptOpenAlgorithmProvider( + &mut alg, + windows::core::w!("SHA256"), + None, + BCRYPT_OPEN_ALGORITHM_PROVIDER_FLAGS(0), + ); + if status.0 != 0 { + return; + } + + for _ in 0..params.iterations.min(100) { + let mut hash_handle = BCRYPT_HASH_HANDLE::default(); + let status = BCryptCreateHash(alg, &mut hash_handle, None, None, 0); + if status.0 != 0 { + continue; + } + + let _ = BCryptHashData(hash_handle, &data, 0); + + let mut output = vec![0u8; 32]; // SHA-256 output + let _ = BCryptFinishHash(hash_handle, &mut output, 0); + + black_box(&output); + let _ = BCryptDestroyHash(hash_handle); + } + + let _ = BCryptCloseAlgorithmProvider(alg, 0); + } +} diff --git a/src/tasks/filesystem.rs b/src/tasks/filesystem.rs new file mode 100644 index 0000000..7eb98c6 --- /dev/null +++ b/src/tasks/filesystem.rs @@ -0,0 +1,96 @@ +use crate::categories::Categories; +use crate::tasks::{TaskDescriptor, TaskParams}; +use rand::rngs::ThreadRng; +use rand::seq::SliceRandom; +use std::hint::black_box; + +pub fn register() -> Vec { + vec![ + TaskDescriptor { + name: "enumerate_system_dir", + category: Categories::FILESYSTEM, + func: enumerate_system_dir, + }, + TaskDescriptor { + name: "enumerate_temp_dir", + category: Categories::FILESYSTEM, + func: enumerate_temp_dir, + }, + TaskDescriptor { + name: "stat_system_files", + category: Categories::FILESYSTEM, + func: stat_system_files, + }, + TaskDescriptor { + name: "read_small_files", + category: Categories::FILESYSTEM, + func: read_small_files, + }, + ] +} + +fn enumerate_system_dir(params: &TaskParams, _rng: &mut ThreadRng) { + let _ = (|| -> std::io::Result<()> { + let dir = std::fs::read_dir(r"C:\Windows\System32")?; + for (i, entry) in dir.enumerate() { + if i >= params.iterations { + break; + } + let entry = entry?; + let meta = entry.metadata()?; + black_box(meta.len()); + } + Ok(()) + })(); +} + +fn enumerate_temp_dir(params: &TaskParams, _rng: &mut ThreadRng) { + let _ = (|| -> std::io::Result<()> { + let dir = std::fs::read_dir(std::env::temp_dir())?; + for (i, entry) in dir.enumerate() { + if i >= params.iterations { + break; + } + let entry = entry?; + let meta = entry.metadata()?; + black_box(meta.len()); + } + Ok(()) + })(); +} + +fn stat_system_files(params: &TaskParams, rng: &mut ThreadRng) { + let paths = [ + r"C:\Windows\explorer.exe", + r"C:\Windows\notepad.exe", + r"C:\Windows\System32\kernel32.dll", + r"C:\Windows\System32\ntdll.dll", + r"C:\Windows\System32\user32.dll", + r"C:\Windows\System32\advapi32.dll", + r"C:\Windows\System32\ws2_32.dll", + r"C:\Windows\System32\cmd.exe", + ]; + for _ in 0..params.iterations.min(200) { + if let Some(path) = paths.choose(rng) { + if let Ok(meta) = std::fs::metadata(path) { + black_box(meta.len()); + } + } + } +} + +fn read_small_files(params: &TaskParams, rng: &mut ThreadRng) { + let paths = [ + r"C:\Windows\System32\drivers\etc\hosts", + r"C:\Windows\System32\drivers\etc\services", + r"C:\Windows\System32\drivers\etc\protocol", + r"C:\Windows\System32\drivers\etc\networks", + ]; + for _ in 0..params.call_depth { + if let Some(path) = paths.choose(rng) { + if let Ok(data) = std::fs::read(path) { + black_box(data.len()); + } + } + } +} diff --git a/src/tasks/memory.rs b/src/tasks/memory.rs new file mode 100644 index 0000000..be797c5 --- /dev/null +++ b/src/tasks/memory.rs @@ -0,0 +1,73 @@ +use crate::categories::Categories; +use crate::tasks::{TaskDescriptor, TaskParams}; +use rand::rngs::ThreadRng; +use rand::{Rng, RngCore}; +use std::hint::black_box; + +pub fn register() -> Vec { + vec![ + TaskDescriptor { + name: "alloc_touch_free", + category: Categories::MEMORY, + func: alloc_touch_free, + }, + TaskDescriptor { + name: "memcpy_chain", + category: Categories::MEMORY, + func: memcpy_chain, + }, + TaskDescriptor { + name: "sort_random_memory", + category: Categories::MEMORY, + func: sort_random_memory, + }, + TaskDescriptor { + name: "pattern_fill_verify", + category: Categories::MEMORY, + func: pattern_fill_verify, + }, + ] +} + +fn alloc_touch_free(params: &TaskParams, rng: &mut ThreadRng) { + let size = params.buffer_size.min(1_048_576); + for _ in 0..params.iterations.min(100) { + let mut buf = vec![0u8; size]; + for offset in (0..size).step_by(4096) { + buf[offset] = rng.gen(); + } + black_box(&buf); + } +} + +fn memcpy_chain(params: &TaskParams, rng: &mut ThreadRng) { + let size = params.buffer_size.min(1_048_576); + let mut src = vec![0u8; size]; + let mut dst = vec![0u8; size]; + rng.fill_bytes(&mut src); + for _ in 0..params.iterations.min(1000) { + dst.copy_from_slice(&src); + std::mem::swap(&mut src, &mut dst); + } + black_box(&src); +} + +fn sort_random_memory(params: &TaskParams, rng: &mut ThreadRng) { + let size = (params.buffer_size / 8).max(1); + for _ in 0..params.call_depth { + let mut data: Vec = (0..size).map(|_| rng.gen()).collect(); + data.sort_unstable(); + black_box(&data); + } +} + +fn pattern_fill_verify(params: &TaskParams, rng: &mut ThreadRng) { + let size = params.buffer_size.min(1_048_576); + let mut buf = vec![0u8; size]; + for _ in 0..params.iterations.min(100) { + let pattern: u8 = rng.gen(); + buf.fill(pattern); + let valid = buf.iter().all(|&b| b == pattern); + black_box(valid); + } +} diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs new file mode 100644 index 0000000..ba30c1d --- /dev/null +++ b/src/tasks/mod.rs @@ -0,0 +1,51 @@ +#[cfg(feature = "cat-compute")] +pub mod compute; +#[cfg(feature = "cat-crypto")] +pub mod crypto; +#[cfg(feature = "cat-filesystem")] +pub mod filesystem; +#[cfg(feature = "cat-memory")] +pub mod memory; +#[cfg(feature = "cat-network")] +pub mod network; +#[cfg(feature = "cat-registry")] +pub mod registry; +#[cfg(feature = "cat-winapi")] +pub mod winapi_tasks; + +use crate::categories::Categories; +use rand::rngs::ThreadRng; + +pub struct TaskParams { + pub iterations: usize, + pub buffer_size: usize, + pub call_depth: usize, +} + +pub type TaskFn = fn(&TaskParams, &mut ThreadRng); + +#[allow(dead_code)] +pub struct TaskDescriptor { + pub name: &'static str, + pub category: Categories, + pub func: TaskFn, +} + +pub fn all_tasks() -> Vec { + let mut tasks = Vec::new(); + #[cfg(feature = "cat-compute")] + tasks.extend(compute::register()); + #[cfg(feature = "cat-memory")] + tasks.extend(memory::register()); + #[cfg(feature = "cat-filesystem")] + tasks.extend(filesystem::register()); + #[cfg(feature = "cat-registry")] + tasks.extend(registry::register()); + #[cfg(feature = "cat-winapi")] + tasks.extend(winapi_tasks::register()); + #[cfg(feature = "cat-network")] + tasks.extend(network::register()); + #[cfg(feature = "cat-crypto")] + tasks.extend(crypto::register()); + tasks +} diff --git a/src/tasks/network.rs b/src/tasks/network.rs new file mode 100644 index 0000000..c63714a --- /dev/null +++ b/src/tasks/network.rs @@ -0,0 +1,115 @@ +use crate::categories::Categories; +use crate::tasks::{TaskDescriptor, TaskParams}; +use rand::rngs::ThreadRng; +use rand::seq::SliceRandom; +use std::hint::black_box; +use std::io::{Read, Write}; +use std::net::{TcpStream, ToSocketAddrs, UdpSocket}; +use std::os::windows::io::AsRawSocket; + +#[link(name = "ws2_32")] +extern "system" { + fn setsockopt(s: usize, level: i32, optname: i32, optval: *const u8, optlen: i32) -> i32; +} + +const SOL_SOCKET: i32 = 0xFFFF; +const SO_RCVTIMEO: i32 = 0x1006; +const SO_SNDTIMEO: i32 = 0x1005; + +fn set_socket_timeouts(socket: &impl AsRawSocket, ms: u32) { + let raw = socket.as_raw_socket() as usize; + let val = ms.to_ne_bytes(); + unsafe { + setsockopt(raw, SOL_SOCKET, SO_RCVTIMEO, val.as_ptr(), 4); + setsockopt(raw, SOL_SOCKET, SO_SNDTIMEO, val.as_ptr(), 4); + } +} + +pub fn register() -> Vec { + vec![ + TaskDescriptor { + name: "dns_lookups", + category: Categories::NETWORK, + func: dns_lookups, + }, + TaskDescriptor { + name: "http_get", + category: Categories::NETWORK, + func: http_get, + }, + TaskDescriptor { + name: "ntp_query", + category: Categories::NETWORK, + func: ntp_query, + }, + ] +} + +fn dns_lookups(params: &TaskParams, rng: &mut ThreadRng) { + let hosts = [ + "google.com:80", + "microsoft.com:80", + "cloudflare.com:80", + "github.com:80", + "amazon.com:80", + "apple.com:80", + "mozilla.org:80", + "wikipedia.org:80", + ]; + for _ in 0..params.iterations.min(50) { + if let Some(host) = hosts.choose(rng) { + let _ = host.to_socket_addrs().map(|addrs| { + for addr in addrs { + black_box(addr); + } + }); + } + } +} + +fn http_get(params: &TaskParams, rng: &mut ThreadRng) { + let targets: &[(&str, u16, &str)] = &[ + ("httpbin.org", 80, "/get"), + ("ip-api.com", 80, "/json"), + ("ifconfig.me", 80, "/ip"), + ]; + for _ in 0..params.call_depth.min(3) { + if let Some(&(host, port, path)) = targets.choose(rng) { + let addr = format!("{}:{}", host, port); + let stream = match TcpStream::connect(&*addr) { + Ok(s) => s, + Err(_) => continue, + }; + set_socket_timeouts(&stream, 3000); + let request = format!( + "GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", + path, host + ); + let mut stream = stream; + if stream.write_all(request.as_bytes()).is_err() { + continue; + } + let mut response = vec![0u8; 4096]; + let _ = stream.read(&mut response); + black_box(&response); + } + } +} + +fn ntp_query(params: &TaskParams, _rng: &mut ThreadRng) { + for _ in 0..params.call_depth.min(3) { + let socket = match UdpSocket::bind("0.0.0.0:0") { + Ok(s) => s, + Err(_) => continue, + }; + set_socket_timeouts(&socket, 3000); + let mut packet = [0u8; 48]; + packet[0] = 0x1B; // NTP v3, client mode + if socket.send_to(&packet, "pool.ntp.org:123").is_err() { + continue; + } + let mut response = [0u8; 48]; + let _ = socket.recv_from(&mut response); + black_box(&response); + } +} diff --git a/src/tasks/registry.rs b/src/tasks/registry.rs new file mode 100644 index 0000000..9978bb1 --- /dev/null +++ b/src/tasks/registry.rs @@ -0,0 +1,201 @@ +use crate::categories::Categories; +use crate::tasks::{TaskDescriptor, TaskParams}; +use rand::rngs::ThreadRng; +use std::hint::black_box; +use windows::Win32::System::Registry::*; + +pub fn register() -> Vec { + vec![ + TaskDescriptor { + name: "read_installed_software", + category: Categories::REGISTRY, + func: read_installed_software, + }, + TaskDescriptor { + name: "read_system_info_reg", + category: Categories::REGISTRY, + func: read_system_info_reg, + }, + TaskDescriptor { + name: "enumerate_services_reg", + category: Categories::REGISTRY, + func: enumerate_services_reg, + }, + TaskDescriptor { + name: "read_timezone_info", + category: Categories::REGISTRY, + func: read_timezone_info, + }, + ] +} + +unsafe fn enum_subkeys(root: HKEY, subkey_path: &[u16], max_keys: usize) { + let mut hkey = HKEY::default(); + let result = RegOpenKeyExW( + root, + windows::core::PCWSTR(subkey_path.as_ptr()), + 0, + KEY_READ, + &mut hkey, + ); + if result.0 != 0 { + return; + } + + let mut name_buf = [0u16; 256]; + for i in 0..max_keys as u32 { + let mut name_len = name_buf.len() as u32; + let result = RegEnumKeyExW( + hkey, + i, + windows::core::PWSTR(name_buf.as_mut_ptr()), + &mut name_len, + None, + windows::core::PWSTR::null(), + None, + None, + ); + if result.0 != 0 { + break; + } + black_box(&name_buf[..name_len as usize]); + } + + let _ = RegCloseKey(hkey); +} + +unsafe fn read_string_value(hkey: HKEY, value_name: &[u16]) { + let mut data_type = REG_VALUE_TYPE::default(); + let mut buf = [0u8; 512]; + let mut buf_len = buf.len() as u32; + let result = RegQueryValueExW( + hkey, + windows::core::PCWSTR(value_name.as_ptr()), + None, + Some(&mut data_type), + Some(buf.as_mut_ptr()), + Some(&mut buf_len), + ); + if result.0 == 0 { + black_box(&buf[..buf_len as usize]); + } +} + +fn read_installed_software(params: &TaskParams, _rng: &mut ThreadRng) { + const PATH: &[u16] = &{ + let mut arr = [0u16; 60]; + let bytes = b"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\0"; + let mut i = 0; + while i < bytes.len() { + arr[i] = bytes[i] as u16; + i += 1; + } + arr + }; + unsafe { + enum_subkeys(HKEY_LOCAL_MACHINE, PATH, params.iterations); + } +} + +fn read_system_info_reg(params: &TaskParams, _rng: &mut ThreadRng) { + const PATH: &[u16] = &{ + let mut arr = [0u16; 50]; + let bytes = b"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\0"; + let mut i = 0; + while i < bytes.len() { + arr[i] = bytes[i] as u16; + i += 1; + } + arr + }; + unsafe { + let mut hkey = HKEY::default(); + let result = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + windows::core::PCWSTR(PATH.as_ptr()), + 0, + KEY_READ, + &mut hkey, + ); + if result.0 != 0 { + return; + } + + let value_names: &[&[u16]] = &[ + &encode_wide_const::<20>(b"ProductName\0"), + &encode_wide_const::<20>(b"CurrentBuild\0"), + &encode_wide_const::<20>(b"EditionID\0"), + ]; + for _ in 0..params.call_depth { + for name in value_names { + read_string_value(hkey, name); + } + } + + let _ = RegCloseKey(hkey); + } +} + +fn enumerate_services_reg(params: &TaskParams, _rng: &mut ThreadRng) { + const PATH: &[u16] = &{ + let mut arr = [0u16; 50]; + let bytes = b"SYSTEM\\CurrentControlSet\\Services\0"; + let mut i = 0; + while i < bytes.len() { + arr[i] = bytes[i] as u16; + i += 1; + } + arr + }; + unsafe { + enum_subkeys(HKEY_LOCAL_MACHINE, PATH, params.iterations); + } +} + +fn read_timezone_info(params: &TaskParams, _rng: &mut ThreadRng) { + const PATH: &[u16] = &{ + let mut arr = [0u16; 60]; + let bytes = b"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\0"; + let mut i = 0; + while i < bytes.len() { + arr[i] = bytes[i] as u16; + i += 1; + } + arr + }; + unsafe { + let mut hkey = HKEY::default(); + let result = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + windows::core::PCWSTR(PATH.as_ptr()), + 0, + KEY_READ, + &mut hkey, + ); + if result.0 != 0 { + return; + } + + let value_names: &[&[u16]] = &[ + &encode_wide_const::<30>(b"TimeZoneKeyName\0"), + &encode_wide_const::<20>(b"StandardName\0"), + ]; + for _ in 0..params.call_depth { + for name in value_names { + read_string_value(hkey, name); + } + } + + let _ = RegCloseKey(hkey); + } +} + +const fn encode_wide_const(bytes: &[u8]) -> [u16; N] { + let mut arr = [0u16; N]; + let mut i = 0; + while i < bytes.len() && i < N { + arr[i] = bytes[i] as u16; + i += 1; + } + arr +} diff --git a/src/tasks/winapi_tasks.rs b/src/tasks/winapi_tasks.rs new file mode 100644 index 0000000..265bf2a --- /dev/null +++ b/src/tasks/winapi_tasks.rs @@ -0,0 +1,109 @@ +use crate::categories::Categories; +use crate::tasks::{TaskDescriptor, TaskParams}; +use rand::rngs::ThreadRng; +use std::hint::black_box; +use windows::Win32::Foundation::*; +use windows::Win32::System::Diagnostics::ToolHelp::*; +use windows::Win32::System::SystemInformation::*; +use windows::Win32::System::DataExchange::*; +use windows::Win32::UI::WindowsAndMessaging::*; + +pub fn register() -> Vec { + vec![ + TaskDescriptor { + name: "enumerate_windows", + category: Categories::WINAPI, + func: enumerate_windows, + }, + TaskDescriptor { + name: "enumerate_processes", + category: Categories::WINAPI, + func: enumerate_processes, + }, + TaskDescriptor { + name: "query_system_info", + category: Categories::WINAPI, + func: query_system_info, + }, + TaskDescriptor { + name: "read_clipboard", + category: Categories::WINAPI, + func: read_clipboard, + }, + ] +} + +unsafe extern "system" fn enum_window_callback(hwnd: HWND, lparam: LPARAM) -> BOOL { + let windows = &mut *(lparam.0 as *mut Vec); + windows.push(hwnd); + BOOL(1) +} + +fn enumerate_windows(params: &TaskParams, _rng: &mut ThreadRng) { + unsafe { + let mut windows: Vec = Vec::new(); + let ptr = &mut windows as *mut Vec as isize; + let _ = EnumWindows(Some(enum_window_callback), LPARAM(ptr)); + + for hwnd in windows.iter().take(params.iterations) { + let mut text = [0u16; 256]; + GetWindowTextW(*hwnd, &mut text); + black_box(&text); + } + } +} + +fn enumerate_processes(params: &TaskParams, _rng: &mut ThreadRng) { + unsafe { + let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + let snapshot = match snapshot { + Ok(h) => h, + Err(_) => return, + }; + + let mut entry = PROCESSENTRY32W { + dwSize: std::mem::size_of::() as u32, + ..Default::default() + }; + + if Process32FirstW(snapshot, &mut entry).is_ok() { + for _ in 0..params.iterations { + black_box(entry.th32ProcessID); + black_box(&entry.szExeFile); + if Process32NextW(snapshot, &mut entry).is_err() { + break; + } + } + } + + let _ = CloseHandle(snapshot); + } +} + +fn query_system_info(params: &TaskParams, _rng: &mut ThreadRng) { + unsafe { + for _ in 0..params.call_depth { + let mut sys_info = SYSTEM_INFO::default(); + GetSystemInfo(&mut sys_info); + black_box(sys_info.dwNumberOfProcessors); + + let mut mem_status = MEMORYSTATUSEX { + dwLength: std::mem::size_of::() as u32, + ..Default::default() + }; + let _ = GlobalMemoryStatusEx(&mut mem_status); + black_box(mem_status.ullTotalPhys); + } + } +} + +fn read_clipboard(params: &TaskParams, _rng: &mut ThreadRng) { + unsafe { + for _ in 0..params.call_depth { + if OpenClipboard(HWND::default()).is_ok() { + let _ = black_box(GetClipboardData(1)); // CF_TEXT + let _ = CloseClipboard(); + } + } + } +} diff --git a/tests/bench.rs b/tests/bench.rs new file mode 100644 index 0000000..1c41a60 --- /dev/null +++ b/tests/bench.rs @@ -0,0 +1,169 @@ +use busywork::{busywork, busywork_with, BusyWork, Categories, Intensity}; +use std::time::Instant; + +fn measure(f: F) -> f64 { + let start = Instant::now(); + f(); + start.elapsed().as_secs_f64() * 1000.0 +} + +fn avg_ms(runs: usize, f: F) -> (f64, f64, f64) { + let mut times = Vec::with_capacity(runs); + for _ in 0..runs { + times.push(measure(|| f())); + } + times.sort_by(|a, b| a.partial_cmp(b).unwrap()); + let min = times[0]; + let max = times[times.len() - 1]; + let avg = times.iter().sum::() / times.len() as f64; + (min, avg, max) +} + +#[test] +fn bench_intensities() { + let runs = 5; + println!("\n{:=<70}", ""); + println!(" INTENSITY BENCHMARKS ({} runs each, all categories)", runs); + println!("{:=<70}", ""); + println!( + " {:10} {:>12} {:>12} {:>12}", + "Level", "Min (ms)", "Avg (ms)", "Max (ms)" + ); + println!("{:-<70}", ""); + + for (name, intensity) in [ + ("Low", Intensity::Low), + ("Medium", Intensity::Medium), + ("High", Intensity::High), + ("Ultra", Intensity::Ultra), + ] { + let (min, avg, max) = avg_ms(runs, || busywork(intensity)); + println!( + " {:10} {:>12.2} {:>12.2} {:>12.2}", + name, min, avg, max + ); + } + println!("{:=<70}\n", ""); +} + +#[test] +fn bench_categories_medium() { + let runs = 5; + println!("\n{:=<70}", ""); + println!( + " CATEGORY BENCHMARKS @ Medium ({} runs each, single category)", + runs + ); + println!("{:=<70}", ""); + println!( + " {:14} {:>12} {:>12} {:>12}", + "Category", "Min (ms)", "Avg (ms)", "Max (ms)" + ); + println!("{:-<70}", ""); + + let cats: &[(&str, Categories)] = &[ + ("COMPUTE", Categories::COMPUTE), + ("MEMORY", Categories::MEMORY), + ("FILESYSTEM", Categories::FILESYSTEM), + ("REGISTRY", Categories::REGISTRY), + ("WINAPI", Categories::WINAPI), + ("NETWORK", Categories::NETWORK), + ("CRYPTO", Categories::CRYPTO), + ]; + + for &(name, cat) in cats { + let (min, avg, max) = avg_ms(runs, || busywork_with(Intensity::Medium, cat)); + println!( + " {:14} {:>12.2} {:>12.2} {:>12.2}", + name, min, avg, max + ); + } + println!("{:=<70}\n", ""); +} + +#[test] +fn bench_categories_high() { + let runs = 3; + println!("\n{:=<70}", ""); + println!( + " CATEGORY BENCHMARKS @ High ({} runs each, single category)", + runs + ); + println!("{:=<70}", ""); + println!( + " {:14} {:>12} {:>12} {:>12}", + "Category", "Min (ms)", "Avg (ms)", "Max (ms)" + ); + println!("{:-<70}", ""); + + let cats: &[(&str, Categories)] = &[ + ("COMPUTE", Categories::COMPUTE), + ("MEMORY", Categories::MEMORY), + ("FILESYSTEM", Categories::FILESYSTEM), + ("REGISTRY", Categories::REGISTRY), + ("WINAPI", Categories::WINAPI), + ("NETWORK", Categories::NETWORK), + ("CRYPTO", Categories::CRYPTO), + ]; + + for &(name, cat) in cats { + let (min, avg, max) = avg_ms(runs, || busywork_with(Intensity::High, cat)); + println!( + " {:14} {:>12.2} {:>12.2} {:>12.2}", + name, min, avg, max + ); + } + println!("{:=<70}\n", ""); +} + +#[test] +fn bench_jitter_variance() { + let runs = 10; + println!("\n{:=<70}", ""); + println!(" JITTER VARIANCE (Medium, COMPUTE only, {} runs)", runs); + println!("{:=<70}", ""); + + let mut with_jitter = Vec::new(); + let mut without_jitter = Vec::new(); + + for _ in 0..runs { + with_jitter.push(measure(|| { + BusyWork::new(Intensity::Medium) + .allow(Categories::COMPUTE) + .jitter(true) + .run(); + })); + without_jitter.push(measure(|| { + BusyWork::new(Intensity::Medium) + .allow(Categories::COMPUTE) + .jitter(false) + .run(); + })); + } + + let variance = |times: &[f64]| -> f64 { + let avg = times.iter().sum::() / times.len() as f64; + let var = times.iter().map(|t| (t - avg).powi(2)).sum::() / times.len() as f64; + var.sqrt() + }; + + let jitter_avg = with_jitter.iter().sum::() / runs as f64; + let no_jitter_avg = without_jitter.iter().sum::() / runs as f64; + let jitter_std = variance(&with_jitter); + let no_jitter_std = variance(&without_jitter); + + println!( + " {:20} {:>10} {:>10}", + "", "Avg (ms)", "StdDev" + ); + println!("{:-<70}", ""); + println!( + " {:20} {:>10.2} {:>10.2}", + "Jitter ON", jitter_avg, jitter_std + ); + println!( + " {:20} {:>10.2} {:>10.2}", + "Jitter OFF", no_jitter_avg, no_jitter_std + ); + println!("{:=<70}\n", ""); +} diff --git a/tests/smoke.rs b/tests/smoke.rs new file mode 100644 index 0000000..66f113c --- /dev/null +++ b/tests/smoke.rs @@ -0,0 +1,54 @@ +use busywork::{busywork, busywork_with, BusyWork, Categories, Intensity}; + +#[test] +fn low_intensity_no_panic() { + busywork(Intensity::Low); +} + +#[test] +fn medium_intensity_no_panic() { + busywork(Intensity::Medium); +} + +#[test] +fn high_compute_only() { + busywork_with(Intensity::High, Categories::COMPUTE); +} + +#[test] +fn high_memory_only() { + busywork_with(Intensity::High, Categories::MEMORY); +} + +#[test] +fn high_filesystem_only() { + busywork_with(Intensity::High, Categories::FILESYSTEM); +} + +#[test] +fn builder_deny_network() { + BusyWork::new(Intensity::Medium) + .deny(Categories::NETWORK) + .run(); +} + +#[test] +fn builder_no_jitter() { + BusyWork::new(Intensity::Low) + .allow(Categories::COMPUTE | Categories::MEMORY) + .jitter(false) + .run(); +} + +#[test] +fn empty_categories_no_panic() { + busywork_with(Intensity::Ultra, Categories::empty()); +} + +#[test] +fn available_returns_all_compiled() { + let avail = Categories::available(); + assert!(avail.contains(Categories::COMPUTE)); + assert!(avail.contains(Categories::MEMORY)); + assert!(avail.contains(Categories::FILESYSTEM)); +}