diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml new file mode 100644 index 0000000..dd84b6d --- /dev/null +++ b/.github/workflows/codspeed.yml @@ -0,0 +1,53 @@ +name: CodSpeed + +on: + push: + branches: [main] + pull_request: + branches: [main] + # `workflow_dispatch` allows CodSpeed to trigger backtest + # performance analysis in order to generate initial data. + workflow_dispatch: + +permissions: + contents: read + id-token: write + +env: + CARGO_TERM_COLOR: always + +jobs: + codspeed: + name: Run benchmarks + runs-on: ubuntu-latest + defaults: + run: + working-directory: src-tauri + steps: + - uses: actions/checkout@v4 + + - name: Install Linux dependencies + working-directory: . + run: > + sudo apt-get update && + sudo apt-get install -y + libwebkit2gtk-4.1-dev + libappindicator3-dev + librsvg2-dev + patchelf + + - name: Setup rust toolchain, cache and cargo-codspeed binary + uses: moonrepo/setup-rust@v0 + with: + channel: stable + cache-target: release + bins: cargo-codspeed + + - name: Build the benchmark target(s) + run: cargo codspeed build + + - name: Run the benchmarks + uses: CodSpeedHQ/action@v4 + with: + mode: simulation + run: cd src-tauri && cargo codspeed run diff --git a/CHANGELOG.md b/CHANGELOG.md index c0da141..35e9f89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **CodSpeed performance benchmarks** (CI): new `domain_benchmarks` Criterion harness in `src-tauri/benches/domain_benchmarks.rs` exercising the pure `domain::model::config` helpers (`apply_patch`, `normalize_link_check_parallelism`, `normalize_max_concurrent`). Wired through a new `.github/workflows/codspeed.yml` workflow that runs the benches under CodSpeed on every PR, providing automated perf-regression tracking for the domain layer. `criterion` + `codspeed-criterion-compat` added as dev-dependencies; `[[bench]]` target declared with `harness = false` so Criterion drives the run. + - **CI hardening** (scope `ci`): new GitHub Actions jobs `secrets-scan` (rejects `.env`/`.pem`/`.key`/etc. tracked files plus `AKIA*`/`sk-ant-*`/`ghp_*`/`AIza*` API key patterns in tracked content), `forbidden-tools` (rejects `pnpm-lock.yaml`/`yarn.lock`, `.eslintrc*`/`biome.json*`/`.prettierrc*` configs, and any `#[allow(dead_code|unused|...)]` / `@ts-ignore` / `@ts-expect-error` / `oxlint-disable` comment), and `changelog-check` (PR-only — fails when `*.rs` / `*.ts` / `*.tsx` change without a matching `CHANGELOG.md` edit). Existing `cargo audit` swapped for `cargo deny check` covering advisories + licenses + bans + sources via the new `deny.toml`. Frontend job now runs `oxfmt --check`, `knip --reporter compact`, and uploads `coverage/` as an artifact. New `mutants.yml` workflow runs `cargo mutants --in-diff` on PRs touching `src-tauri/**` and a 4-shard nightly sweep on `main`. - **Release verification** (scope `ci`): new `verify-tag` job in `release.yml` blocks the build matrix when `Cargo.toml`, `package.json`, the pushed tag, and the `CHANGELOG.md` section are out of sync. - **Hook hardening** (scope `ci`): pre-commit gains `oxfmt --check` (scoped via new `.oxfmtignore` to keep `src-tauri/gen/`, `contrib/`, `registry/`, `*.md` untouched), `scripts/no-secrets.sh` (file-pattern + diff-content scan), `scripts/no-manual-deps.sh` (rejects manual `Cargo.toml` / `package.json` dep edits without an updated lock — enforces `cargo add` / `npm install`), and `changelog-updated`. Pre-push gains `cargo deny check licenses bans advisories` and `knip --reporter compact`. New `knip.json` keeps `files` / `dependencies` / `unlisted` / `binaries` / `unresolved` as `error` and demotes `exports` / `types` / `nsExports` / `nsTypes` / `duplicates` / `enumMembers` to `warn` for backwards-compatible adoption. `src-tauri/.cargo/mutants.toml` excludes the binary entrypoint, `build.rs`, `tests/**`, and the SQLite migrations from cargo-mutants and bumps `timeout_multiplier` to 2.0 / `minimum_test_timeout` to 60s to absorb Tauri/sea-orm/extism cold-build latency. `package.json` adds `knip ^6.11` and `oxfmt ^0.47` as devDependencies. diff --git a/README.md b/README.md index 4593ad8..22cda1a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Built with Tauri](https://img.shields.io/badge/built%20with-Tauri%202-24c8db?logo=tauri)](https://tauri.app) [![Rust](https://img.shields.io/badge/rust-1.95-orange?logo=rust)](src-tauri/Cargo.toml) [![CI](https://github.com/mpiton/vortex/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mpiton/vortex/actions/workflows/ci.yml) +[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/mpiton/vortex?utm_source=badge) Open-source desktop download manager — successor to JDownloader. Tauri 2 + Rust backend + React 19 frontend, hexagonal architecture, CQRS, WASM plugin system (Extism). diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1768fad..e39917d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -114,6 +114,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "1.0.0" @@ -150,7 +156,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -161,7 +167,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -170,6 +176,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arbitrary" version = "1.4.2" @@ -832,6 +847,12 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cbc" version = "0.1.2" @@ -933,6 +954,33 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -1016,12 +1064,80 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "codspeed" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ce4a32373a5c84f4fa785099b300b9b1796311abc122b9eb62c65fa615145d" +dependencies = [ + "anyhow", + "cc", + "colored", + "getrandom 0.2.17", + "glob", + "libc", + "nix 0.31.2", + "serde", + "serde_json", + "statrs", +] + +[[package]] +name = "codspeed-criterion-compat" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5557c4e023f427ba208b849193c51c2ebd40534828490cd3f5f7f7fc0284ce5" +dependencies = [ + "clap", + "codspeed", + "codspeed-criterion-compat-walltime", + "colored", + "regex", +] + +[[package]] +name = "codspeed-criterion-compat-walltime" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ac19f0a5c3542301e9d011d658f93c3f550698ec8ba7d7c072692e7924c401" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "codspeed", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + [[package]] name = "combine" version = "4.6.7" @@ -1318,6 +1434,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1689,7 +1815,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1938,7 +2064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3301,12 +3427,32 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -3685,7 +3831,7 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" dependencies = [ - "nix", + "nix 0.29.0", "serde", "winapi", ] @@ -3963,6 +4109,18 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -4025,7 +4183,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4283,6 +4441,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -4365,7 +4529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.45.0", + "windows-sys 0.61.2", ] [[package]] @@ -4775,6 +4939,34 @@ dependencies = [ "time", ] +[[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 = "png" version = "0.17.16" @@ -5016,7 +5208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5672,7 +5864,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5741,7 +5933,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6424,7 +6616,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6714,6 +6906,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "statrs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" +dependencies = [ + "approx", + "num-traits", +] + [[package]] name = "string_cache" version = "0.8.9" @@ -7353,7 +7555,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7491,6 +7693,16 @@ dependencies = [ "zerovec", ] +[[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 = "tinyvec" version = "1.11.0" @@ -7861,7 +8073,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -8130,6 +8342,7 @@ dependencies = [ "bincode", "bytes", "bzip2", + "codspeed-criterion-compat", "dashmap", "digest 0.11.2", "dirs", @@ -8606,7 +8819,7 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "gimli", - "itertools", + "itertools 0.14.0", "log", "object", "pulley-interpreter", @@ -9042,7 +9255,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1f669fd..941b0c8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -69,5 +69,10 @@ tauri-plugin-pilot = "0.4.0" windows-sys = { version = "0.59", features = ["Win32_Storage_FileSystem"] } [dev-dependencies] +criterion = { version = "4.6.0", package = "codspeed-criterion-compat" } tempfile = "3.27.0" wiremock = "0.6.5" + +[[bench]] +name = "domain_benchmarks" +harness = false diff --git a/src-tauri/benches/domain_benchmarks.rs b/src-tauri/benches/domain_benchmarks.rs new file mode 100644 index 0000000..13bf827 --- /dev/null +++ b/src-tauri/benches/domain_benchmarks.rs @@ -0,0 +1,302 @@ +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use vortex_lib::domain::model::config::{ + apply_patch, normalize_link_check_parallelism, normalize_max_concurrent, +}; +use vortex_lib::domain::model::link::LinkStatus; +use vortex_lib::domain::model::{ + AppConfig, ChecksumAlgorithm, ConfigPatch, Download, DownloadId, Priority, Segment, Url, +}; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +fn make_download() -> Download { + let url = Url::new("https://cdn.example.com/releases/v2.0/archive.tar.gz").unwrap(); + Download::new( + DownloadId(1), + url, + "archive.tar.gz".to_string(), + "/tmp".to_string(), + ) +} + +// --------------------------------------------------------------------------- +// URL parsing +// --------------------------------------------------------------------------- + +fn bench_url_parsing(c: &mut Criterion) { + let mut group = c.benchmark_group("url_parsing"); + + group.bench_function("simple_https", |b| { + b.iter(|| Url::new(black_box("https://example.com/file.zip"))) + }); + + group.bench_function("complex_with_port_and_path", |b| { + b.iter(|| { + Url::new(black_box( + "https://cdn.example.com:8443/releases/v2.0/archive.tar.gz?token=abc123#section", + )) + }) + }); + + group.bench_function("with_userinfo", |b| { + b.iter(|| Url::new(black_box("https://user:pass@cdn.example.com/file.bin"))) + }); + + group.bench_function("ftp_scheme", |b| { + b.iter(|| Url::new(black_box("ftp://mirror.example.org/pub/release.iso"))) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Download state machine +// --------------------------------------------------------------------------- + +fn bench_download_state_machine(c: &mut Criterion) { + let mut group = c.benchmark_group("download_state_machine"); + + group.bench_function("create_new", |b| { + b.iter(|| { + let url = Url::new("https://example.com/file.zip").unwrap(); + black_box(Download::new( + DownloadId(1), + url, + "file.zip".to_string(), + "/tmp".to_string(), + )) + }) + }); + + group.bench_function("full_lifecycle", |b| { + b.iter(|| { + let mut d = make_download(); + let _ = d.start(); + let _ = d.pause(); + let _ = d.resume(); + let _ = d.complete(); + black_box(&d); + }) + }); + + group.bench_function("start_and_fail_retry_cycle", |b| { + b.iter(|| { + let mut d = make_download().with_max_retries(3); + let _ = d.start(); + let _ = d.fail("network timeout".to_string()); + let _ = d.retry(); + let _ = d.start(); + let _ = d.fail("connection reset".to_string()); + let _ = d.retry(); + black_box(&d); + }) + }); + + group.bench_function("progress_percentage", |b| { + let mut d = make_download(); + d.set_file_size(10_000_000); + d.update_progress(7_500_000); + b.iter(|| black_box(d.progress_percentage())) + }); + + group.bench_function("checksum_workflow", |b| { + b.iter(|| { + let mut d = make_download() + .with_expected_checksum( + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string(), + ) + .unwrap(); + let _ = d.start(); + let _ = d.start_checking(); + let _ = d.record_checksum_match( + ChecksumAlgorithm::Sha256, + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string(), + ); + black_box(&d); + }) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Segment operations +// --------------------------------------------------------------------------- + +fn bench_segment_operations(c: &mut Criterion) { + let mut group = c.benchmark_group("segment_operations"); + + group.bench_function("create_new", |b| { + b.iter(|| black_box(Segment::new(1, DownloadId(10), 0, 10_485_760))) + }); + + group.bench_function("full_lifecycle", |b| { + b.iter(|| { + let mut s = Segment::new(1, DownloadId(10), 0, 10_485_760); + let _ = s.start(); + s.update_progress(5_000_000); + let _ = s.complete(); + black_box(&s); + }) + }); + + group.bench_function("split", |b| { + b.iter(|| { + let mut s = Segment::new(1, DownloadId(10), 0, 10_485_760); + let _ = s.start(); + s.update_progress(2_000_000); + let _ = s.split(5_242_880, 42); + black_box(&s); + }) + }); + + group.bench_function("error_and_reset_cycle", |b| { + b.iter(|| { + let mut s = Segment::new(1, DownloadId(10), 0, 10_485_760); + let _ = s.start(); + s.update_progress(1_000_000); + let _ = s.fail("timeout".to_string()); + let _ = s.reset(); + let _ = s.start(); + let _ = s.complete(); + black_box(&s); + }) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Checksum detection +// --------------------------------------------------------------------------- + +fn bench_checksum_detection(c: &mut Criterion) { + let mut group = c.benchmark_group("checksum_detection"); + + group.bench_function("detect_sha256", |b| { + b.iter(|| { + ChecksumAlgorithm::detect_from_hex(black_box( + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + )) + }) + }); + + group.bench_function("detect_md5", |b| { + b.iter(|| ChecksumAlgorithm::detect_from_hex(black_box("d41d8cd98f00b204e9800998ecf8427e"))) + }); + + group.bench_function("reject_invalid", |b| { + b.iter(|| ChecksumAlgorithm::detect_from_hex(black_box("not-a-valid-hex-string"))) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Link status +// --------------------------------------------------------------------------- + +fn bench_link_status(c: &mut Criterion) { + let mut group = c.benchmark_group("link_status"); + + group.bench_function("from_status_code_200", |b| { + b.iter(|| LinkStatus::from_status_code(black_box(200))) + }); + + group.bench_function("from_status_code_404", |b| { + b.iter(|| LinkStatus::from_status_code(black_box(404))) + }); + + group.bench_function("from_status_code_500", |b| { + b.iter(|| LinkStatus::from_status_code(black_box(500))) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Config operations +// --------------------------------------------------------------------------- + +fn bench_config_operations(c: &mut Criterion) { + let mut group = c.benchmark_group("config_operations"); + + group.bench_function("default_config", |b| { + b.iter(|| black_box(AppConfig::default())) + }); + + group.bench_function("apply_patch_single_field", |b| { + let patch = ConfigPatch { + max_concurrent_downloads: Some(8), + ..Default::default() + }; + b.iter(|| { + let mut config = AppConfig::default(); + apply_patch(&mut config, black_box(&patch)); + black_box(&config); + }) + }); + + group.bench_function("apply_patch_many_fields", |b| { + let patch = ConfigPatch { + max_concurrent_downloads: Some(8), + max_segments_per_download: Some(16), + speed_limit_bytes_per_sec: Some(Some(10_485_760)), + max_retries: Some(10), + theme: Some("dark".to_string()), + locale: Some("fr".to_string()), + dynamic_split_enabled: Some(true), + verify_checksums: Some(true), + link_check_parallelism: Some(16), + link_check_timeout_secs: Some(30), + ..Default::default() + }; + b.iter(|| { + let mut config = AppConfig::default(); + apply_patch(&mut config, black_box(&patch)); + black_box(&config); + }) + }); + + group.bench_function("normalize_max_concurrent", |b| { + b.iter(|| black_box(normalize_max_concurrent(black_box(10)))) + }); + + group.bench_function("normalize_link_check_parallelism", |b| { + b.iter(|| black_box(normalize_link_check_parallelism(black_box(32)))) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Priority +// --------------------------------------------------------------------------- + +fn bench_priority(c: &mut Criterion) { + let mut group = c.benchmark_group("priority"); + + group.bench_function("create_valid", |b| { + b.iter(|| black_box(Priority::new(black_box(5)))) + }); + + group.bench_function("create_invalid", |b| { + b.iter(|| black_box(Priority::new(black_box(0)))) + }); + + group.finish(); +} + +criterion_group!( + benches, + bench_url_parsing, + bench_download_state_machine, + bench_segment_operations, + bench_checksum_detection, + bench_link_status, + bench_config_operations, + bench_priority, +); +criterion_main!(benches);