diff --git a/Cargo.lock b/Cargo.lock index 2c5763c..52df5be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,12 +61,47 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.21" @@ -76,6 +111,23 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "bitflags 1.3.2", + "textwrap", + "unicode-width", +] + [[package]] name = "clap" version = "4.5.37" @@ -122,6 +174,88 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "criterion" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +dependencies = [ + "atty", + "cast", + "clap 2.34.0", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +dependencies = [ + "memchr", +] + [[package]] name = "diff" version = "0.1.13" @@ -132,9 +266,11 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" name = "dix" version = "0.1.0" dependencies = [ - "clap", + "clap 4.5.37", + "criterion", "diff", "env_logger", + "libc", "log", "regex", "rusqlite", @@ -142,6 +278,12 @@ dependencies = [ "yansi", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "env_filter" version = "0.1.3" @@ -183,6 +325,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "hashbrown" version = "0.15.3" @@ -207,12 +355,36 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "jiff" version = "0.2.12" @@ -237,6 +409,28 @@ dependencies = [ "syn", ] +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + [[package]] name = "libsqlite3-sys" version = "0.33.0" @@ -260,18 +454,61 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "portable-atomic" version = "1.11.0" @@ -305,6 +542,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" version = "1.11.1" @@ -340,7 +597,7 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b" dependencies = [ - "bitflags", + "bitflags 2.9.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -348,6 +605,27 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.219" @@ -357,6 +635,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.219" @@ -368,6 +656,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" @@ -397,6 +697,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "2.0.12" @@ -417,12 +726,28 @@ dependencies = [ "syn", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "utf8parse" version = "0.2.2" @@ -435,6 +760,115 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index eaaceee..95a9578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,15 @@ name = "dix" version = "0.1.0" edition = "2024" +[[bin]] +name = "dix" +path = "src/main.rs" + +[lib] +name = "dixlib" +path = "src/lib.rs" + + [dependencies] clap = { version = "4.5.37", features = ["derive"] } regex = "1.11.1" @@ -12,3 +21,19 @@ log = "0.4.20" env_logger = "0.11.3" rusqlite = { version = "0.35.0", features = ["bundled"] } diff = "0.1.13" + +[dev-dependencies] +criterion = "0.3" +libc = "0.2" + +[[bench]] +name = "store" +harness=false + +[[bench]] +name = "print" +harness=false + +[[bench]] +name = "util" +harness=false diff --git a/benches/common.rs b/benches/common.rs new file mode 100644 index 0000000..debf5d3 --- /dev/null +++ b/benches/common.rs @@ -0,0 +1,89 @@ +use std::{ + env, + fs::{self, DirEntry}, + path::PathBuf, + sync::OnceLock, +}; + +use dixlib::{store, util::PackageDiff}; + +/// tries to get the path of the oldest nixos system derivation +/// this function is pretty hacky and only used so that +/// you don't have to specify a specific derivation to +/// run the benchmarks +fn get_oldest_nixos_system() -> Option { + let profile_dir = fs::read_dir("/nix/var/nix/profiles").ok()?; + + let files = profile_dir.filter_map(Result::ok).filter_map(|entry| { + entry + .file_type() + .ok() + .and_then(|f| f.is_symlink().then_some(entry.path())) + }); + + files.min_by_key(|path| { + // extract all digits from the file name and use that as key + let p = path.as_os_str().to_str().unwrap_or_default(); + let digits: String = p.chars().filter(|c| c.is_ascii_digit()).collect(); + // if we are not able to produce a key (e.g. because the path does not contain digits) + // we put it last + digits.parse::().unwrap_or(u32::MAX) + }) +} + +pub fn get_deriv_query() -> &'static PathBuf { + static _QUERY_DERIV: OnceLock = OnceLock::new(); + _QUERY_DERIV.get_or_init(|| { + let path = PathBuf::from( + env::var("DIX_BENCH_NEW_SYSTEM") + .unwrap_or_else(|_| "/run/current-system/system".into()), + ); + path + }) +} +pub fn get_deriv_query_old() -> &'static PathBuf { + static _QUERY_DERIV: OnceLock = OnceLock::new(); + _QUERY_DERIV.get_or_init(|| { + let path = env::var("DIX_BENCH_OLD_SYSTEM") + .ok() + .map(PathBuf::from) + .or(get_oldest_nixos_system()) + .unwrap_or_else(|| PathBuf::from("/run/current-system/system")); + path + }) +} + +pub fn get_packages() -> &'static (Vec, Vec) { + static _PKGS: OnceLock<(Vec, Vec)> = OnceLock::new(); + _PKGS.get_or_init(|| { + let pkgs_before = store::get_packages(std::path::Path::new(get_deriv_query_old())) + .unwrap() + .into_iter() + .map(|(_, name)| name) + .collect::>(); + let pkgs_after = store::get_packages(std::path::Path::new(get_deriv_query())) + .unwrap() + .into_iter() + .map(|(_, name)| name) + .collect::>(); + (pkgs_before, pkgs_after) + }) +} + +pub fn get_pkg_diff() -> &'static PackageDiff<'static> { + static _PKG_DIFF: OnceLock = OnceLock::new(); + _PKG_DIFF.get_or_init(|| { + let (pkgs_before, pkgs_after) = get_packages(); + PackageDiff::new(pkgs_before, pkgs_after) + }) +} + +/// prints the old and new NixOs system used for benchmarking +/// +/// is used to give information about the old and new system +pub fn print_used_nixos_systems() { + let old = get_deriv_query_old(); + let new = get_deriv_query(); + println!("old system used {:?}", old); + println!("new system used {:?}", new); +} diff --git a/benches/print.rs b/benches/print.rs new file mode 100644 index 0000000..af2832c --- /dev/null +++ b/benches/print.rs @@ -0,0 +1,86 @@ +mod common; + +use std::{fs::File, os::fd::AsRawFd}; + +use common::{get_pkg_diff, print_used_nixos_systems}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use dixlib::print; + +/// reroutes stdout and stderr to the null device before +/// executing `f` +fn suppress_output(f: F) { + let stdout = std::io::stdout(); + let stderr = std::io::stderr(); + + // Save original FDs + let orig_stdout_fd = stdout.as_raw_fd(); + let orig_stderr_fd = stderr.as_raw_fd(); + + // Open /dev/null and get its FD + let devnull = File::create("/dev/null").unwrap(); + let null_fd = devnull.as_raw_fd(); + + // Redirect stdout and stderr to /dev/null + let _ = unsafe { libc::dup2(null_fd, orig_stdout_fd) }; + let _ = unsafe { libc::dup2(null_fd, orig_stderr_fd) }; + + f(); + + let _ = unsafe { libc::dup2(orig_stdout_fd, 1) }; + let _ = unsafe { libc::dup2(orig_stderr_fd, 2) }; +} + +pub fn bench_print_added(c: &mut Criterion) { + print_used_nixos_systems(); + let diff = get_pkg_diff(); + c.bench_function("print_added", |b| { + b.iter(|| { + suppress_output(|| { + print::print_added( + black_box(&diff.added), + black_box(&diff.pkg_to_versions_post), + 30, + ); + }); + }); + }); +} +pub fn bench_print_removed(c: &mut Criterion) { + print_used_nixos_systems(); + let diff = get_pkg_diff(); + c.bench_function("print_removed", |b| { + b.iter(|| { + suppress_output(|| { + print::print_removed( + black_box(&diff.removed), + black_box(&diff.pkg_to_versions_pre), + 30, + ); + }); + }); + }); +} +pub fn bench_print_changed(c: &mut Criterion) { + print_used_nixos_systems(); + let diff = get_pkg_diff(); + c.bench_function("print_changed", |b| { + b.iter(|| { + suppress_output(|| { + print::print_changes( + black_box(&diff.changed), + black_box(&diff.pkg_to_versions_pre), + black_box(&diff.pkg_to_versions_post), + 30, + ); + }); + }); + }); +} + +criterion_group!( + benches, + bench_print_added, + bench_print_removed, + bench_print_changed +); +criterion_main!(benches); diff --git a/benches/store.rs b/benches/store.rs new file mode 100644 index 0000000..cac7b52 --- /dev/null +++ b/benches/store.rs @@ -0,0 +1,36 @@ +mod common; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use dixlib::store; + +// basic benchmarks using the current system +// +// problem: this is not reproducible at all +// since this is very depending on the current +// system and the nature of the system in general +// +// we might want to think about using a copy of the sqlite +// db to benchmark instead to make the results comparable + +pub fn bench_get_packages(c: &mut Criterion) { + c.bench_function("get_packages", |b| { + b.iter(|| store::get_packages(black_box(common::get_deriv_query()))); + }); +} +pub fn bench_get_closure_size(c: &mut Criterion) { + c.bench_function("get_closure_size", |b| { + b.iter(|| store::get_closure_size(black_box(common::get_deriv_query()))); + }); +} +pub fn bench_get_dependency_graph(c: &mut Criterion) { + c.bench_function("get_dependency_graph", |b| { + b.iter(|| store::get_dependency_graph(black_box(common::get_deriv_query()))); + }); +} + +criterion_group!( + benches, + bench_get_packages, + bench_get_closure_size, + bench_get_dependency_graph +); +criterion_main!(benches); diff --git a/benches/util.rs b/benches/util.rs new file mode 100644 index 0000000..ec8eb19 --- /dev/null +++ b/benches/util.rs @@ -0,0 +1,15 @@ +mod common; + +use common::get_packages; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use dixlib::util::PackageDiff; + +pub fn bench_package_diff(c: &mut Criterion) { + let (pkgs_before, pkgs_after) = get_packages(); + c.bench_function("PackageDiff::new", |b| { + b.iter(|| PackageDiff::new(black_box(pkgs_before), black_box(pkgs_after))); + }); +} + +criterion_group!(benches, bench_package_diff); +criterion_main!(benches); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f820eb9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +pub mod error; +pub mod print; +pub mod store; +pub mod util; diff --git a/src/main.rs b/src/main.rs index 084e440..aa63a1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,14 @@ -mod print; -mod util; use clap::Parser; use core::str; +use dixlib::print; +use dixlib::store; +use dixlib::util::PackageDiff; use log::{debug, error}; use std::{ collections::{HashMap, HashSet}, thread, }; use yansi::Paint; -mod error; -mod store; -use error::AppError; - -// Use type alias for Result with our custom error type -type Result = std::result::Result; #[derive(Parser, Debug)] #[command(name = "dix")] @@ -145,42 +140,15 @@ fn main() { } }; - // Map from packages of the first closure to their version - let mut pre = HashMap::<&str, HashSet<&str>>::new(); - let mut post = HashMap::<&str, HashSet<&str>>::new(); - - for p in &package_list_pre { - match util::get_version(&**p) { - Ok((name, version)) => { - pre.entry(name).or_default().insert(version); - } - Err(e) => { - debug!("Error parsing package version: {e}"); - } - } - } - - for p in &package_list_post { - match util::get_version(&**p) { - Ok((name, version)) => { - post.entry(name).or_default().insert(version); - } - Err(e) => { - debug!("Error parsing package version: {e}"); - } - } - } - - // Compare the package names of both versions - let pre_keys: HashSet<&str> = pre.keys().copied().collect(); - let post_keys: HashSet<&str> = post.keys().copied().collect(); - - // Difference gives us added and removed packages - let added: HashSet<&str> = &post_keys - &pre_keys; - - let removed: HashSet<&str> = &pre_keys - &post_keys; - // Get the intersection of the package names for version changes - let changed: HashSet<&str> = &pre_keys & &post_keys; + let PackageDiff { + pkg_to_versions_pre: pre, + pkg_to_versions_post: post, + pre_keys: _, + post_keys: _, + added, + removed, + changed, + } = PackageDiff::new(&package_list_pre, &package_list_post); debug!("Added packages: {}", added.len()); debug!("Removed packages: {}", removed.len()); diff --git a/src/util.rs b/src/util.rs index a0a57f9..6a34273 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,18 +1,18 @@ -use crate::error::AppError; -use log::{debug, error}; -use regex::Regex; -use std::cmp::Ordering; - use std::{ + cmp::Ordering, collections::{HashMap, HashSet}, - string::ToString, sync::OnceLock, - thread, }; +use crate::error::AppError; +use log::debug; +use regex::Regex; + // Use type alias for Result with our custom error type type Result = std::result::Result; +use std::string::ToString; + #[derive(Eq, PartialEq, Debug)] enum VersionComponent { Number(u64), @@ -104,29 +104,6 @@ pub fn compare_versions(a: &str, b: &str) -> Ordering { iter_a.cmp(iter_b) } -mod test { - - #[test] - fn test_version_component_iter() { - use super::VersionComponent::{Number, Text}; - use crate::util::VersionComponentIterator; - let v = "132.1.2test234-1-man----.--.......---------..---"; - - let comp: Vec<_> = VersionComponentIterator::new(v).collect(); - assert_eq!( - comp, - [ - Number(132), - Number(1), - Number(2), - Text("test".into()), - Number(234), - Number(1), - Text("man".into()) - ] - ); - } -} /// Parses a nix store path to extract the packages name and version /// /// This function first drops the inputs first 44 chars, since that is exactly the length of the /nix/store/... prefix. Then it matches that against our store path regex. @@ -174,10 +151,96 @@ pub fn get_version<'a>(pack: impl Into<&'a str>) -> Result<(&'a str, &'a str)> { // Returns a reference to the compiled regex pattern. // The regex is compiled only once. -fn store_path_regex() -> &'static Regex { +pub fn store_path_regex() -> &'static Regex { static REGEX: OnceLock = OnceLock::new(); REGEX.get_or_init(|| { Regex::new(r"(.+?)(-([0-9].*?))?$") .expect("Failed to compile regex pattern for nix store paths") }) } + +// TODO: move this somewhere else, this does not really +// belong into this file +pub struct PackageDiff<'a> { + pub pkg_to_versions_pre: HashMap<&'a str, HashSet<&'a str>>, + pub pkg_to_versions_post: HashMap<&'a str, HashSet<&'a str>>, + pub pre_keys: HashSet<&'a str>, + pub post_keys: HashSet<&'a str>, + pub added: HashSet<&'a str>, + pub removed: HashSet<&'a str>, + pub changed: HashSet<&'a str>, +} + +impl<'a> PackageDiff<'a> { + pub fn new + 'a>(pkgs_pre: &'a [S], pkgs_post: &'a [S]) -> Self { + // Map from packages of the first closure to their version + let mut pre = HashMap::<&str, HashSet<&str>>::new(); + let mut post = HashMap::<&str, HashSet<&str>>::new(); + + for p in pkgs_pre { + match get_version(p.as_ref()) { + Ok((name, version)) => { + pre.entry(name).or_default().insert(version); + } + Err(e) => { + debug!("Error parsing package version: {e}"); + } + } + } + + for p in pkgs_post { + match get_version(p.as_ref()) { + Ok((name, version)) => { + post.entry(name).or_default().insert(version); + } + Err(e) => { + debug!("Error parsing package version: {e}"); + } + } + } + + // Compare the package names of both versions + let pre_keys: HashSet<&str> = pre.keys().copied().collect(); + let post_keys: HashSet<&str> = post.keys().copied().collect(); + + // Difference gives us added and removed packages + let added: HashSet<&str> = &post_keys - &pre_keys; + + let removed: HashSet<&str> = &pre_keys - &post_keys; + // Get the intersection of the package names for version changes + let changed: HashSet<&str> = &pre_keys & &post_keys; + Self { + pkg_to_versions_pre: pre, + pkg_to_versions_post: post, + pre_keys, + post_keys, + added, + removed, + changed, + } + } +} + +mod test { + + #[test] + fn test_version_component_iter() { + use super::VersionComponent::{Number, Text}; + use crate::util::VersionComponentIterator; + let v = "132.1.2test234-1-man----.--.......---------..---"; + + let comp: Vec<_> = VersionComponentIterator::new(v).collect(); + assert_eq!( + comp, + [ + Number(132), + Number(1), + Number(2), + Text("test".into()), + Number(234), + Number(1), + Text("man".into()) + ] + ); + } +}