From 751b225b24292741d6cce9f83c25fe8a77135486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Tr=E1=BA=A7n?= Date: Wed, 14 Oct 2026 01:44:34 +0700 Subject: [PATCH] feat(benchmark): complete reproducibility milestones --- .github/workflows/benchmark.yml | 10 +- .github/workflows/benchmark_smoke.yml | 49 ++++++ Cargo.lock | 72 +++++++++ Cargo.toml | 1 + TODO.md | 8 +- crates/collector/src/cgroups.rs | 123 ++++++++++++++- crates/collector/src/sampler.rs | 90 +++++------ crates/collector/tests/sampler_test.rs | 26 ++++ crates/common/src/result.rs | 12 ++ crates/common/tests/result_test.rs | 4 + crates/orchestrator/Cargo.toml | 1 + crates/orchestrator/src/container.rs | 117 ++++++++------ crates/orchestrator/src/container_config.rs | 143 ++++++++++++++++++ crates/orchestrator/src/lib.rs | 1 + crates/orchestrator/src/main.rs | 29 +++- crates/orchestrator/src/provenance.rs | 68 +++++++++ crates/orchestrator/src/runner.rs | 11 ++ crates/reporter/src/chart.rs | 117 ++++++++++---- crates/reporter/src/index.rs | 12 +- crates/reporter/src/logic.rs | 128 +++++++++++++--- crates/reporter/src/markdown.rs | 47 +++++- crates/reporter/tests/readme_test.rs | 1 + crates/reporter/tests/reporter_test.rs | 60 +++++--- docs/architecture.md | 2 +- docs/how_to_use.md | 11 +- docs/roadmap/phase5_deployment.md | 3 +- docs/schemas/result_schema.json | 6 +- flake.nix | 11 +- .../web-server/cpp/agensio/Dockerfile.glibc | 4 +- .../web-server/cpp/agensio/Dockerfile.musl | 4 +- .../web-server/cpp/drogon/Dockerfile.glibc | 4 +- .../web-server/cpp/drogon/Dockerfile.musl | 4 +- .../web-server/cpp/hical/Dockerfile.glibc | 4 +- .../web-server/cpp/hical/Dockerfile.musl | 4 +- .../cpp/uwebsockets/Dockerfile.glibc | 4 +- .../cpp/uwebsockets/Dockerfile.musl | 4 +- .../csharp/aspnet-minimal/Dockerfile.glibc | 4 +- .../csharp/aspnet-minimal/Dockerfile.musl | 4 +- .../csharp/cadente/Dockerfile.glibc | 4 +- .../web-server/csharp/cadente/Dockerfile.musl | 4 +- .../csharp/genhttp/Dockerfile.glibc | 4 +- .../web-server/csharp/genhttp/Dockerfile.musl | 4 +- .../web-server/go/fasthttp/Dockerfile.glibc | 4 +- .../web-server/go/fasthttp/Dockerfile.musl | 4 +- .../web-server/go/fib-tuned/Dockerfile.glibc | 4 +- .../web-server/go/fib-tuned/Dockerfile.musl | 4 +- .../web-server/go/fiber/Dockerfile.glibc | 4 +- .../web-server/go/fiber/Dockerfile.musl | 4 +- .../web-server/java/rapidoid/Dockerfile.glibc | 4 +- .../web-server/java/rapidoid/Dockerfile.musl | 4 +- .../web-server/java/undertow/Dockerfile.glibc | 4 +- .../web-server/java/undertow/Dockerfile.musl | 4 +- .../web-server/java/vertx/Dockerfile.glibc | 4 +- .../web-server/java/vertx/Dockerfile.musl | 4 +- .../javascript/bun/Dockerfile.glibc | 2 +- .../web-server/javascript/bun/Dockerfile.musl | 2 +- .../javascript/elysia/Dockerfile.glibc | 2 +- .../javascript/elysia/Dockerfile.musl | 2 +- .../javascript/fulmine/Dockerfile.glibc | 2 +- .../javascript/fulmine/Dockerfile.musl | 2 +- .../php/frankenphp/Dockerfile.glibc | 2 +- .../web-server/php/frankenphp/Dockerfile.musl | 2 +- .../web-server/php/swoole/Dockerfile.glibc | 2 +- .../web-server/php/swoole/Dockerfile.musl | 2 +- .../web-server/php/workerman/Dockerfile.glibc | 2 +- .../web-server/php/workerman/Dockerfile.musl | 2 +- .../python/blacksheep/Dockerfile.glibc | 2 +- .../python/blacksheep/Dockerfile.musl | 2 +- .../python/granian/Dockerfile.glibc | 2 +- .../web-server/python/granian/Dockerfile.musl | 2 +- .../web-server/python/robyn/Dockerfile.glibc | 2 +- .../web-server/python/robyn/Dockerfile.musl | 2 +- .../web-server/ruby/agoo/Dockerfile.glibc | 2 +- .../web-server/ruby/agoo/Dockerfile.musl | 2 +- .../web-server/ruby/falcon/Dockerfile.glibc | 2 +- .../web-server/ruby/falcon/Dockerfile.musl | 2 +- .../web-server/ruby/iodine/Dockerfile.glibc | 2 +- .../web-server/ruby/iodine/Dockerfile.musl | 2 +- .../rust/actix-web/Dockerfile.glibc | 4 +- .../web-server/rust/actix-web/Dockerfile.musl | 4 +- .../web-server/rust/cpeak/Dockerfile.glibc | 4 +- .../web-server/rust/cpeak/Dockerfile.musl | 4 +- .../web-server/rust/ntex/Dockerfile.glibc | 4 +- .../web-server/rust/ntex/Dockerfile.musl | 4 +- .../web-server/zig/dusty/Dockerfile.glibc | 4 +- .../web-server/zig/dusty/Dockerfile.musl | 4 +- .../web-server/zig/swerver/Dockerfile.glibc | 4 +- .../web-server/zig/swerver/Dockerfile.musl | 4 +- .../zig/uwebzockets/Dockerfile.glibc | 4 +- .../zig/uwebzockets/Dockerfile.musl | 4 +- scripts/run_benchmarks_all.sh | 3 + scripts/verify_host.sh | 105 ++++++++++--- templates/docker/Dockerfile.glibc | 2 +- templates/docker/Dockerfile.musl | 2 +- 94 files changed, 1131 insertions(+), 344 deletions(-) create mode 100644 .github/workflows/benchmark_smoke.yml create mode 100644 crates/orchestrator/src/container_config.rs create mode 100644 crates/orchestrator/src/provenance.rs diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 562b3a0..c5070d0 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -18,9 +18,13 @@ concurrency: jobs: benchmark: name: Run Benchmark Suite - runs-on: [self-hosted, linux, benchmark] + runs-on: [self-hosted, linux, x64, benchmark-dedicated] env: BENCHMARK_FRAMEWORK_FILTER: ${{ inputs.framework_filter || 'all' }} + BENCHMARK_DEDICATED_HOST: '1' + BENCHMARK_TARGET_CPUS: '2-7' + BENCHMARK_LOAD_CPUS: '8-15' + BENCHMARK_CGROUP_PARENT: framework-benchmark steps: - name: Checkout repository uses: actions/checkout@v4 @@ -41,13 +45,13 @@ jobs: ${{ runner.os }}-cargo-benchmark- - name: Verify host environment - run: sh scripts/verify_host.sh + run: sh scripts/verify_host.sh --require-dedicated - name: Build benchmark orchestrator run: cargo build --release --bin orchestrator - name: Run orchestrator - run: ./target/release/orchestrator frameworks results + run: taskset --cpu-list "$BENCHMARK_LOAD_CPUS" ./target/release/orchestrator frameworks results - name: Upload benchmark artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/benchmark_smoke.yml b/.github/workflows/benchmark_smoke.yml new file mode 100644 index 0000000..5c4496f --- /dev/null +++ b/.github/workflows/benchmark_smoke.yml @@ -0,0 +1,49 @@ +name: Benchmark Smoke + +on: + pull_request: + paths: + - frameworks/** + - templates/docker/** + - scripts/** + - flake.nix + - .github/workflows/benchmark_smoke.yml + push: + branches: [main] + paths: + - frameworks/** + - templates/docker/** + - scripts/** + - flake.nix + - .github/workflows/benchmark_smoke.yml + workflow_dispatch: + +permissions: + contents: read + +jobs: + smoke: + name: Dual-libc cpeak health smoke + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Build and exercise both variants + shell: bash + working-directory: frameworks/web-server/rust/cpeak + run: | + set -euo pipefail + for libc in glibc musl; do + image="benchmark-smoke:cpeak-${libc}" + name="benchmark-smoke-${libc}" + docker build --file "Dockerfile.${libc}" --tag "$image" . + docker run --detach --rm --network host --name "$name" "$image" + for _ in $(seq 1 20); do + curl --fail --silent --show-error http://127.0.0.1:8080/healthz && break + sleep 1 + done + test "$(curl --fail --silent http://127.0.0.1:8080/plaintext)" = 'Hello, World!' + docker stop --time 5 "$name" + done diff --git a/Cargo.lock b/Cargo.lock index 1b29885..948a3e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,15 @@ version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +[[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 = "byteorder" version = "1.5.0" @@ -63,6 +72,15 @@ dependencies = [ "toml", ] +[[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.2" @@ -87,6 +105,26 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" +[[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 = "equivalent" version = "1.0.2" @@ -114,6 +152,16 @@ dependencies = [ "zlib-rs", ] +[[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 = "hashbrown" version = "0.17.1" @@ -243,6 +291,7 @@ dependencies = [ "common", "load-gen", "reporter", + "sha2", "thiserror", "tokio", ] @@ -371,6 +420,17 @@ dependencies = [ "serde", ] +[[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 = "signal-hook-registry" version = "1.4.8" @@ -503,12 +563,24 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[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.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" +[[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" diff --git a/Cargo.toml b/Cargo.toml index ac5425a..6256029 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ hdrhistogram = "7.5" bytes = "1.10" http = "1.2" httparse = "1.10" +sha2 = "0.10" [profile.release] opt-level = 3 diff --git a/TODO.md b/TODO.md index 339e172..80b4a1e 100644 --- a/TODO.md +++ b/TODO.md @@ -24,7 +24,7 @@ The Docker-based dual-libc baseline is implemented, but a result is publishable 3. **Phase 2: Dual-Libc Infrastructure & Engine Core**: [docs/roadmap/phase2_load_engine.md](docs/roadmap/phase2_load_engine.md) [Completed] - Nix flakes, Docker multi-stage templates, Tokio/Httparse load generator, and cgroups v2 harvester. 4. **Phase 3: Framework Registry & Baseline Implementations**: [docs/roadmap/phase3_frameworks.md](docs/roadmap/phase3_frameworks.md) [Completed] - - Framework registry populated with 32 candidate frameworks across 10 languages (Zig, Rust, Go, C#, C++, JS/TS, Ruby, Java, PHP, Python) with dual-libc Dockerfiles and valid manifests. + - Framework registry populated with 31 candidate frameworks across 10 languages (Zig, Rust, Go, C#, C++, JS/TS, Ruby, Java, PHP, Python) with dual-libc Dockerfiles and valid manifests. 5. **Phase 4: Aggregation, Metrics Processing & Markdown Engine**: [docs/roadmap/phase4_reporter.md](docs/roadmap/phase4_reporter.md) [Completed] - Pure aggregation logic, SVG bar charts, append-only historical index, and Markdown table generator. 6. **Phase 5: Bare-Metal Deployment & GitHub CI/CD**: [docs/roadmap/phase5_deployment.md](docs/roadmap/phase5_deployment.md) [Completed] @@ -43,9 +43,9 @@ The Docker-based dual-libc baseline is implemented, but a result is publishable ## Next milestones -- Host-managed cgroups and CPU sets for complete collector attribution. -- Full-matrix execution on a labelled self-hosted runner; deterministic smoke validation in hosted CI. -- Immutable image and framework input pinning before historical comparison. +- Host-managed cgroups and CPU sets for complete collector attribution. [Completed] +- Full-matrix execution on a labelled self-hosted runner. [Requires designated host] +- Immutable image and framework input pinning before historical comparison. [Completed] See [docs/how_to_use.md](docs/how_to_use.md) for the supported host workflow. diff --git a/crates/collector/src/cgroups.rs b/crates/collector/src/cgroups.rs index f2210d4..a143712 100644 --- a/crates/collector/src/cgroups.rs +++ b/crates/collector/src/cgroups.rs @@ -1,4 +1,7 @@ use crate::error::CollectorError; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom}; +use std::path::Path; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct CgroupSample { @@ -8,22 +11,126 @@ pub struct CgroupSample { pub system_usec: u64, } +#[derive(Debug)] +pub struct CgroupFiles { + memory_current: Option, + memory_peak: Option, + cpu_stat: Option, +} + pub fn parse_cgroup_memory_stat(content: &str) -> Result { content .trim() .parse::() - .map_err(|e| CollectorError::Parse(format!("invalid cgroup memory stat: {e}"))) + .map_err(|error| CollectorError::Parse(format!("invalid cgroup memory stat: {error}"))) } pub fn parse_cgroup_cpu_stat(content: &str) -> (u64, u64) { - let mut user = 0; - let mut system = 0; + let mut user_usec = 0; + let mut system_usec = 0; for line in content.lines() { - if let Some(rest) = line.strip_prefix("user_usec ") { - user = rest.trim().parse().unwrap_or(0); - } else if let Some(rest) = line.strip_prefix("system_usec ") { - system = rest.trim().parse().unwrap_or(0); + if let Some(value) = line.strip_prefix("user_usec ") { + user_usec = value.trim().parse().unwrap_or(0); + } else if let Some(value) = line.strip_prefix("system_usec ") { + system_usec = value.trim().parse().unwrap_or(0); + } + } + (user_usec, system_usec) +} + +pub fn open_cgroup_files(path: &Path) -> CgroupFiles { + CgroupFiles { + memory_current: File::open(path.join("memory.current")).ok(), + memory_peak: File::open(path.join("memory.peak")).ok(), + cpu_stat: File::open(path.join("cpu.stat")).ok(), + } +} + +pub fn sample_cgroup_files(files: &mut CgroupFiles) -> CgroupSample { + let current_bytes = read_number(&mut files.memory_current); + let peak_bytes = read_number(&mut files.memory_peak); + let (user_usec, system_usec) = read_cpu_stat(&mut files.cpu_stat); + CgroupSample { + current_bytes, + peak_bytes, + user_usec, + system_usec, + } +} + +fn read_number(file: &mut Option) -> u64 { + let Some(file) = file.as_mut() else { + return 0; + }; + let mut buffer = [0; 32]; + if file.seek(SeekFrom::Start(0)).is_err() { + return 0; + } + let Ok(count) = file.read(&mut buffer) else { + return 0; + }; + parse_number_bytes(&buffer[..count]) +} + +fn read_cpu_stat(file: &mut Option) -> (u64, u64) { + let Some(file) = file.as_mut() else { + return (0, 0); + }; + let mut buffer = [0; 256]; + if file.seek(SeekFrom::Start(0)).is_err() { + return (0, 0); + } + let Ok(count) = file.read(&mut buffer) else { + return (0, 0); + }; + let Ok(content) = std::str::from_utf8(&buffer[..count]) else { + return (0, 0); + }; + parse_cgroup_cpu_stat(content) +} + +fn parse_number_bytes(content: &[u8]) -> u64 { + let mut value = 0_u64; + for byte in content { + if !byte.is_ascii_digit() { + break; } + value = value + .saturating_mul(10) + .saturating_add(u64::from(byte - b'0')); + } + value +} + +#[cfg(test)] +mod tests { + use super::{CgroupSample, open_cgroup_files, sample_cgroup_files}; + use std::fs; + + #[test] + fn cgroup_files_should_sample_all_controllers() { + let path = std::env::temp_dir().join(format!("collector-cgroups-{}", std::process::id())); + fs::create_dir_all(&path).expect("create cgroup fixture directory"); + fs::write(path.join("memory.current"), "4096\n").expect("write current memory fixture"); + fs::write(path.join("memory.peak"), "8192\n").expect("write peak memory fixture"); + fs::write( + path.join("cpu.stat"), + "usage_usec 10\nuser_usec 7\nsystem_usec 3\n", + ) + .expect("write cpu fixture"); + + let mut files = open_cgroup_files(&path); + let sample = sample_cgroup_files(&mut files); + fs::remove_dir_all(&path).expect("remove cgroup fixture directory"); + + assert_eq!( + sample, + CgroupSample { + current_bytes: 4096, + peak_bytes: 8192, + user_usec: 7, + system_usec: 3, + } + ); } - (user, system) } diff --git a/crates/collector/src/sampler.rs b/crates/collector/src/sampler.rs index ff1f03b..bd78b81 100644 --- a/crates/collector/src/sampler.rs +++ b/crates/collector/src/sampler.rs @@ -1,5 +1,5 @@ use crate::aggregator::HarvestAggregator; -use crate::cgroups::parse_cgroup_memory_stat; +use crate::cgroups::{CgroupFiles, open_cgroup_files, sample_cgroup_files}; use crate::metrics::{RuntimeCounters, apply_runtime_counters}; use crate::procfs::{parse_statm, parse_status_switches}; use crate::rapl::{RaplZoneSnapshot, energy_delta_joules, read_rapl_snapshot}; @@ -13,6 +13,8 @@ use std::time::{Duration, Instant}; use tokio::sync::Mutex; use tokio::task::JoinHandle; +const CGROUP_INTERVAL: Duration = Duration::from_millis(100); + #[derive(Debug)] pub struct BackgroundSampler { running: Arc, @@ -25,9 +27,9 @@ pub struct BackgroundSampler { } #[derive(Debug)] -struct RssSource { - file: Option, - is_cgroup: bool, +struct Sources { + cgroups: Option, + statm: Option, } #[derive(Debug, Default)] @@ -44,29 +46,27 @@ impl BackgroundSampler { ) -> Self { let running = Arc::new(AtomicBool::new(true)); let aggregator = Arc::new(Mutex::new(HarvestAggregator::default())); - - let run_flag = Arc::clone(&running); - let agg_lock = Arc::clone(&aggregator); - - let mut source = open_rss_source(target_pid, cgroup_dir); let counters = counter_paths(target_pid, cgroup_dir); let counter_start = read_counters(&counters); - let rapl_start = read_rapl_snapshot(Path::new("/sys/class/powercap")).unwrap_or_default(); + let run_flag = Arc::clone(&running); + let agg_lock = Arc::clone(&aggregator); + let mut sources = open_sources(target_pid, cgroup_dir); + let interval = cgroup_dir.map_or(interval, |_| CGROUP_INTERVAL); let handle = tokio::spawn(async move { let mut ticker = tokio::time::interval(interval); while run_flag.load(Ordering::Relaxed) { ticker.tick().await; - let sample_rss = sample_rss_bytes(&mut source); + let (memory_bytes, peak_bytes) = sample_memory(&mut sources); let mut agg = agg_lock.lock().await; - agg.record_sample(sample_rss); + agg.record_sample(memory_bytes); + agg.peak_rss_bytes = agg.peak_rss_bytes.max(peak_bytes); } }); - Self { running, aggregator, handle: Some(handle), - rapl_start, + rapl_start: read_rapl_snapshot(Path::new("/sys/class/powercap")).unwrap_or_default(), counters, counter_start, started: Instant::now(), @@ -76,19 +76,15 @@ impl BackgroundSampler { pub fn spawn_simulated(interval: Duration, static_rss: u64) -> Self { let running = Arc::new(AtomicBool::new(true)); let aggregator = Arc::new(Mutex::new(HarvestAggregator::default())); - let run_flag = Arc::clone(&running); let agg_lock = Arc::clone(&aggregator); - let handle = tokio::spawn(async move { let mut ticker = tokio::time::interval(interval); while run_flag.load(Ordering::Relaxed) { ticker.tick().await; - let mut agg = agg_lock.lock().await; - agg.record_sample(static_rss); + agg_lock.lock().await.record_sample(static_rss); } }); - Self { running, aggregator, @@ -102,11 +98,10 @@ impl BackgroundSampler { pub async fn stop(mut self) -> ResourceUsage { self.running.store(false, Ordering::Relaxed); - if let Some(h) = self.handle.take() { - let _ = h.await; + if let Some(handle) = self.handle.take() { + let _ = handle.await; } - let agg = self.aggregator.lock().await; - let mut usage = agg.clone().finalize(); + let mut usage = self.aggregator.lock().await.clone().finalize(); let elapsed_usec = self.started.elapsed().as_micros() as u64; apply_runtime_counters( &mut usage, @@ -123,9 +118,10 @@ impl BackgroundSampler { } fn counter_paths(target_pid: Option, cgroup_dir: Option<&Path>) -> CounterPaths { - let cpu_stat = cgroup_dir.map(|path| path.join("cpu.stat")); - let status = target_pid.map(|pid| PathBuf::from(format!("/proc/{pid}/status"))); - CounterPaths { cpu_stat, status } + CounterPaths { + cpu_stat: cgroup_dir.map(|path| path.join("cpu.stat")), + status: target_pid.map(|pid| PathBuf::from(format!("/proc/{pid}/status"))), + } } fn read_counters(paths: &CounterPaths) -> RuntimeCounters { @@ -139,10 +135,8 @@ fn read_counters(paths: &CounterPaths) -> RuntimeCounters { .status .as_ref() .and_then(|path| std::fs::read_to_string(path).ok()) - .map(|content| { - let (voluntary, involuntary) = parse_status_switches(&content); - voluntary.saturating_add(involuntary) - }) + .map(|content| parse_status_switches(&content)) + .map(|(voluntary, involuntary)| voluntary.saturating_add(involuntary)) .unwrap_or_default(); RuntimeCounters { user_usec, @@ -151,30 +145,27 @@ fn read_counters(paths: &CounterPaths) -> RuntimeCounters { } } -fn open_rss_source(target_pid: Option, cgroup_dir: Option<&Path>) -> RssSource { - if let Some(cg) = cgroup_dir { - let mem_path = cg.join("memory.current"); - if let Ok(file) = File::open(mem_path) { - return RssSource { - file: Some(file), - is_cgroup: true, - }; - } +fn open_sources(target_pid: Option, cgroup_dir: Option<&Path>) -> Sources { + let pid = target_pid.map_or_else(|| "self".to_owned(), |pid| pid.to_string()); + Sources { + cgroups: cgroup_dir.map(open_cgroup_files), + statm: File::open(format!("/proc/{pid}/statm")).ok(), } +} - let pid_str = target_pid.map_or_else(|| "self".to_string(), |p| p.to_string()); - let statm_path = format!("/proc/{pid_str}/statm"); - RssSource { - file: File::open(statm_path).ok(), - is_cgroup: false, - } +fn sample_memory(sources: &mut Sources) -> (u64, u64) { + let process_rss = read_statm(&mut sources.statm); + let cgroup = sources.cgroups.as_mut().map(sample_cgroup_files); + let current = cgroup.map_or(0, |sample| sample.current_bytes); + let peak = cgroup.map_or(0, |sample| sample.peak_bytes); + (process_rss.max(current), process_rss.max(peak)) } -fn sample_rss_bytes(source: &mut RssSource) -> u64 { - let Some(file) = source.file.as_mut() else { +fn read_statm(file: &mut Option) -> u64 { + let Some(file) = file.as_mut() else { return 0; }; - let mut buffer = [0u8; 128]; + let mut buffer = [0; 128]; if file.seek(SeekFrom::Start(0)).is_err() { return 0; } @@ -184,8 +175,5 @@ fn sample_rss_bytes(source: &mut RssSource) -> u64 { let Ok(content) = std::str::from_utf8(&buffer[..count]) else { return 0; }; - if source.is_cgroup { - return parse_cgroup_memory_stat(content).unwrap_or(0); - } parse_statm(content, 4096).unwrap_or(0) } diff --git a/crates/collector/tests/sampler_test.rs b/crates/collector/tests/sampler_test.rs index 7d0de06..2a5fad7 100644 --- a/crates/collector/tests/sampler_test.rs +++ b/crates/collector/tests/sampler_test.rs @@ -1,4 +1,5 @@ use collector::BackgroundSampler; +use std::fs; use std::time::Duration; #[tokio::test] @@ -10,3 +11,28 @@ async fn test_background_sampler_lifecycle() { assert_eq!(usage.peak_rss_bytes, 1024 * 1024); assert_eq!(usage.avg_rss_bytes, 1024 * 1024); } + +#[tokio::test] +async fn target_sampler_should_combine_process_and_cgroup_memory() { + let path = std::env::temp_dir().join(format!("collector-sampler-{}", std::process::id())); + let peak = 17_179_869_184_u64; + fs::create_dir_all(&path).expect("create cgroup fixture directory"); + fs::write(path.join("memory.current"), "1024\n").expect("write current memory fixture"); + fs::write(path.join("memory.peak"), format!("{peak}\n")).expect("write peak memory fixture"); + fs::write( + path.join("cpu.stat"), + "usage_usec 10\nuser_usec 7\nsystem_usec 3\n", + ) + .expect("write cpu fixture"); + + let sampler = BackgroundSampler::spawn_target( + Some(std::process::id()), + Some(&path), + Duration::from_millis(1), + ); + tokio::time::sleep(Duration::from_millis(10)).await; + let usage = sampler.stop().await; + fs::remove_dir_all(&path).expect("remove cgroup fixture directory"); + + assert!(usage.peak_rss_bytes == peak && usage.avg_rss_bytes > 1024); +} diff --git a/crates/common/src/result.rs b/crates/common/src/result.rs index 025cccf..d69a514 100644 --- a/crates/common/src/result.rs +++ b/crates/common/src/result.rs @@ -47,6 +47,14 @@ pub struct ResourceUsage { pub struct RunProvenance { pub revision: String, pub image_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub framework_input_sha256: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub target_cpus: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub load_cpus: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cgroup_path: Option, } impl Default for RunProvenance { @@ -54,6 +62,10 @@ impl Default for RunProvenance { Self { revision: "unknown".into(), image_id: None, + framework_input_sha256: None, + target_cpus: None, + load_cpus: None, + cgroup_path: None, } } } diff --git a/crates/common/tests/result_test.rs b/crates/common/tests/result_test.rs index abe6606..89d9d5c 100644 --- a/crates/common/tests/result_test.rs +++ b/crates/common/tests/result_test.rs @@ -23,6 +23,10 @@ fn test_roundtrip_result_serialization() { provenance: RunProvenance { revision: "abc123".into(), image_id: Some("sha256:abc123".into()), + framework_input_sha256: Some("def456".into()), + target_cpus: Some("2-7".into()), + load_cpus: Some("8-15".into()), + cgroup_path: Some("/sys/fs/cgroup/framework-benchmark".into()), }, benchmarks: vec![TestMetrics { category: "plaintext".into(), diff --git a/crates/orchestrator/Cargo.toml b/crates/orchestrator/Cargo.toml index de8b572..04404cb 100644 --- a/crates/orchestrator/Cargo.toml +++ b/crates/orchestrator/Cargo.toml @@ -12,6 +12,7 @@ collector = { path = "../collector" } reporter = { path = "../reporter" } tokio = { workspace = true } thiserror = { workspace = true } +sha2 = { workspace = true } [lints] workspace = true diff --git a/crates/orchestrator/src/container.rs b/crates/orchestrator/src/container.rs index 96e34c8..cc267f3 100644 --- a/crates/orchestrator/src/container.rs +++ b/crates/orchestrator/src/container.rs @@ -1,19 +1,19 @@ +#[path = "container_config.rs"] +mod container_config; use crate::error::OrchestratorError; use common::manifest::FrameworkManifest; use common::result::LibcVariant; use std::env; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; - #[derive(Debug, Clone)] pub struct ContainerTarget { pub name: String, pub pid: u32, - pub cgroup_dir: Option, + pub cgroup_dir: Option, pub image_id: String, } - pub fn start_container( framework_dir: &Path, manifest: &FrameworkManifest, @@ -29,6 +29,14 @@ pub fn start_container( LibcVariant::Musl => docker.dockerfile_musl.as_deref(), } .ok_or_else(|| OrchestratorError::Process("framework lacks requested libc image".into()))?; + let cpuset_cpus = + container_config::optional_config("BENCHMARK_TARGET_CPUS", container_config::valid_cpuset)?; + let cgroup_parent = container_config::optional_config( + "BENCHMARK_CGROUP_PARENT", + container_config::valid_cgroup_parent, + )?; + let shutdown_signal = + container_config::shutdown_signal(manifest.runtime.shutdown_signal.as_deref())?; let suffix = match libc { LibcVariant::Glibc => "glibc", LibcVariant::Musl => "musl", @@ -40,8 +48,6 @@ pub fn start_container( let base = sanitize_name(&manifest.framework.name); let image = format!("framework-benchmark/{base}:{suffix}"); let name = format!("benchmark-{base}-{suffix}-{stamp}"); - let workers = benchmark_workers(); - let build_args = crate::build_options::docker_build_args(dockerfile, &image)?; run_docker(framework_dir, &build_args)?; let image_id = image_id(framework_dir, &image)?; @@ -54,39 +60,46 @@ pub fn start_container( "framework-benchmark.managed=true".into(), "--network".into(), "host".into(), + "--stop-signal".into(), + shutdown_signal.into(), "--env".into(), - format!("BENCHMARK_WORKERS={workers}"), + format!("BENCHMARK_WORKERS={}", benchmark_workers()), ]; + args.extend(container_config::isolation_args( + cpuset_cpus.as_deref(), + cgroup_parent.as_deref(), + )); if small_host() { - args.push("--cpus".into()); - args.push("1".into()); - args.push("--memory".into()); - args.push("2g".into()); - args.push("--pids-limit".into()); - args.push("128".into()); + args.extend(["--cpus", "1", "--memory", "2g", "--pids-limit", "128"].map(String::from)); } if let Some(database_url) = database_url { - args.push("--env".into()); - args.push(format!("DATABASE_URL={database_url}")); + args.extend(["--env".into(), format!("DATABASE_URL={database_url}")]); } args.push(image); - let output = run_docker(framework_dir, &args)?; - let id = output.trim(); - let pid = match container_pid(framework_dir, id) { + let id = run_docker(framework_dir, &args)?.trim().to_owned(); + let pid = match container_pid(framework_dir, &id) { Ok(pid) => pid, - Err(error) => { - let _ = stop_container(framework_dir, &name); - return Err(error); - } + Err(error) => return start_error(framework_dir, &name, error), + }; + let cgroup_dir = match cgroup_dir_for_pid(pid) { + Ok(path) => path, + Err(error) => return start_error(framework_dir, &name, error), }; Ok(ContainerTarget { name, pid, - cgroup_dir: cgroup_dir_for_pid(pid), + cgroup_dir: Some(cgroup_dir), image_id, }) } - +fn start_error( + framework_dir: &Path, + name: &str, + error: OrchestratorError, +) -> Result { + let _ = stop_container(framework_dir, name); + Err(error) +} fn image_id(framework_dir: &Path, image: &str) -> Result { let id = run_docker( framework_dir, @@ -100,41 +113,48 @@ fn image_id(framework_dir: &Path, image: &str) -> Result Result<(), OrchestratorError> { - run_docker(framework_dir, &["rm", "--force", name]).map(|_| ()) + let stopped = run_docker(framework_dir, &container_config::stop_args(name)); + let removed = run_docker(framework_dir, &["rm", name]); + if removed.is_ok() { + return Ok(()); + } + if run_docker(framework_dir, &["rm", "--force", name]).is_ok() { + return Ok(()); + } + Err(stopped.and(removed).unwrap_err()) } - fn container_pid(framework_dir: &Path, id: &str) -> Result { run_docker(framework_dir, &["inspect", "--format={{.State.Pid}}", id])? .trim() .parse() .map_err(|error| OrchestratorError::Process(format!("invalid container pid: {error}"))) } - -fn cgroup_dir_for_pid(pid: u32) -> Option { - let path = format!("/proc/{pid}/cgroup"); - let content = std::fs::read_to_string(path).ok()?; - cgroup_dir_from_content(&content) +fn cgroup_dir_for_pid(pid: u32) -> Result { + let content = std::fs::read_to_string(format!("/proc/{pid}/cgroup"))?; + let path = cgroup_dir_from_content(&content) + .ok_or_else(|| OrchestratorError::Process("container lacks a unified cgroup".into()))?; + if path.is_dir() { + return Ok(path); + } + Err(OrchestratorError::Process( + "container cgroup path is unavailable".into(), + )) } - -fn cgroup_dir_from_content(content: &str) -> Option { +fn cgroup_dir_from_content(content: &str) -> Option { let cgroup = content.lines().find_map(|line| line.strip_prefix("0::"))?; - Some(std::path::Path::new("/sys/fs/cgroup").join(cgroup.trim_start_matches('/'))) + Some(Path::new("/sys/fs/cgroup").join(cgroup.trim_start_matches('/'))) } - fn benchmark_workers() -> usize { - let configured = env::var("BENCHMARK_WORKERS") + env::var("BENCHMARK_WORKERS") .ok() .and_then(|value| value.parse().ok()) - .filter(|value: &usize| *value > 0); - configured.unwrap_or_else(|| std::thread::available_parallelism().map_or(1, usize::from)) + .filter(|value: &usize| *value > 0) + .unwrap_or_else(|| std::thread::available_parallelism().map_or(1, usize::from)) } - fn small_host() -> bool { env::var("BENCHMARK_SMALL_HOST").is_ok_and(|value| value == "1") } - fn run_docker>(cwd: &Path, args: &[S]) -> Result { let runtime = env::var("CONTAINER_RUNTIME").unwrap_or_else(|_| "docker".into()); let output = Command::new(runtime) @@ -148,7 +168,6 @@ fn run_docker>(cwd: &Path, args: &[S]) -> Result String { name.chars() .map(|char| { @@ -160,18 +179,20 @@ fn sanitize_name(name: &str) -> String { }) .collect() } - #[cfg(test)] mod tests { - use super::cgroup_dir_from_content; - use std::path::Path; - + use super::*; #[test] fn cgroup_directory_uses_the_unified_hierarchy() { - let content = "11:memory:/legacy\n0::/system.slice/docker.scope\n"; assert_eq!( - cgroup_dir_from_content(content).as_deref(), + cgroup_dir_from_content("11:memory:/legacy\n0::/system.slice/docker.scope\n") + .as_deref(), Some(Path::new("/sys/fs/cgroup/system.slice/docker.scope")) ); } + + #[test] + fn names_replace_container_unsafe_characters() { + assert_eq!(sanitize_name("rust/axum"), "rust-axum"); + } } diff --git a/crates/orchestrator/src/container_config.rs b/crates/orchestrator/src/container_config.rs new file mode 100644 index 0000000..95c92da --- /dev/null +++ b/crates/orchestrator/src/container_config.rs @@ -0,0 +1,143 @@ +use crate::error::OrchestratorError; +use std::env; + +pub(super) fn optional_config( + name: &str, + validator: fn(&str) -> bool, +) -> Result, OrchestratorError> { + let value = match env::var(name) { + Ok(value) => value, + Err(env::VarError::NotPresent) => return Ok(None), + Err(error) => { + return Err(OrchestratorError::Process(format!( + "invalid {name}: {error}" + ))); + } + }; + if validator(&value) { + return Ok(Some(value)); + } + Err(OrchestratorError::Process(format!("invalid {name}"))) +} + +pub(super) fn valid_cpuset(value: &str) -> bool { + !value.is_empty() + && value.split(',').all(|part| match part.split_once('-') { + Some((start, end)) => cpu_index(start) + .zip(cpu_index(end)) + .is_some_and(|(start, end)| start <= end), + None => cpu_index(part).is_some(), + }) +} + +pub(super) fn valid_cgroup_parent(value: &str) -> bool { + let value = value.strip_prefix('/').unwrap_or(value); + !value.is_empty() + && value.split('/').all(|part| { + !part.is_empty() + && part != "." + && part != ".." + && part + .chars() + .all(|char| !char.is_control() && !char.is_whitespace()) + }) +} + +pub(super) fn shutdown_signal(value: Option<&str>) -> Result<&str, OrchestratorError> { + let value = value.unwrap_or("SIGTERM"); + if valid_shutdown_signal(value) { + return Ok(value); + } + Err(OrchestratorError::Process(format!( + "invalid shutdown signal: {value}" + ))) +} + +pub(super) fn isolation_args( + cpuset_cpus: Option<&str>, + cgroup_parent: Option<&str>, +) -> Vec { + let mut args = Vec::with_capacity(4); + if let Some(cpus) = cpuset_cpus { + args.extend(["--cpuset-cpus".into(), cpus.into()]); + } + if let Some(parent) = cgroup_parent { + args.extend(["--cgroup-parent".into(), parent.into()]); + } + args +} + +pub(super) fn stop_args(name: &str) -> [&str; 4] { + ["stop", "--time", "5", name] +} + +fn cpu_index(value: &str) -> Option { + (!value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit())) + .then(|| value.parse().ok()) + .flatten() +} + +fn valid_shutdown_signal(value: &str) -> bool { + value + .parse::() + .is_ok_and(|signal| signal > 0 && signal <= 64 && signal != 9 && signal != 19) + || matches!( + value, + "SIGHUP" + | "SIGINT" + | "SIGQUIT" + | "SIGILL" + | "SIGTRAP" + | "SIGABRT" + | "SIGBUS" + | "SIGFPE" + | "SIGUSR1" + | "SIGSEGV" + | "SIGUSR2" + | "SIGPIPE" + | "SIGALRM" + | "SIGTERM" + | "SIGCHLD" + | "SIGCONT" + | "SIGTSTP" + | "SIGTTIN" + | "SIGTTOU" + | "SIGURG" + | "SIGXCPU" + | "SIGXFSZ" + | "SIGVTALRM" + | "SIGPROF" + | "SIGWINCH" + | "SIGIO" + | "SIGPWR" + | "SIGSYS" + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn isolation_values_accept_only_safe_forms() { + assert!(valid_cpuset("0,2-4") && valid_cgroup_parent("benchmark.slice/workload")); + assert!(!valid_cpuset("0,,2") && !valid_cpuset("4-2")); + assert!(!valid_cgroup_parent("../escape")); + } + + #[test] + fn shutdown_signal_defaults_and_rejects_unknown_values() { + assert_eq!(shutdown_signal(None).unwrap(), "SIGTERM"); + assert!(shutdown_signal(Some("SIGUNKNOWN")).is_err()); + assert!(shutdown_signal(Some("SIGKILL")).is_err()); + } + + #[test] + fn docker_arguments_include_explicit_isolation_and_timeout() { + assert_eq!( + isolation_args(Some("2-7"), Some("benchmark.slice")), + ["--cpuset-cpus", "2-7", "--cgroup-parent", "benchmark.slice"] + ); + assert_eq!(stop_args("target"), ["stop", "--time", "5", "target"]); + } +} diff --git a/crates/orchestrator/src/lib.rs b/crates/orchestrator/src/lib.rs index be69c42..e657c26 100644 --- a/crates/orchestrator/src/lib.rs +++ b/crates/orchestrator/src/lib.rs @@ -4,6 +4,7 @@ pub mod error; pub mod lifecycle; pub mod machine; pub mod poller; +mod provenance; pub mod runner; mod workload; pub mod ws_bench; diff --git a/crates/orchestrator/src/main.rs b/crates/orchestrator/src/main.rs index 1445361..6a17a66 100644 --- a/crates/orchestrator/src/main.rs +++ b/crates/orchestrator/src/main.rs @@ -16,10 +16,9 @@ async fn main() -> Result<(), OrchestratorError> { println!("==> Farben Framework Benchmark Orchestrator v0.1.0"); println!("==> Scanning framework registry at: {frameworks_dir}"); - let frameworks = select_frameworks( - discover_frameworks(Path::new(frameworks_dir))?, - &env::var("BENCHMARK_FRAMEWORK_FILTER").unwrap_or_else(|_| "all".into()), - )?; + let filter = env::var("BENCHMARK_FRAMEWORK_FILTER").unwrap_or_else(|_| "all".into()); + require_dedicated_host_for_matrix(&filter)?; + let frameworks = select_frameworks(discover_frameworks(Path::new(frameworks_dir))?, &filter)?; if frameworks.is_empty() { println!("==> No framework manifests found in {frameworks_dir}."); println!("==> Framework discovery completed without targets."); @@ -118,3 +117,25 @@ fn selected_libcs() -> Result, OrchestratorError> { ))), } } + +fn require_dedicated_host_for_matrix(filter: &str) -> Result<(), OrchestratorError> { + if !filter.trim().is_empty() && filter.trim() != "all" { + return Ok(()); + } + if env::var("BENCHMARK_DEDICATED_HOST").is_ok_and(|value| value == "1") { + return Ok(()); + } + Err(OrchestratorError::Process( + "full matrix requires BENCHMARK_DEDICATED_HOST=1".into(), + )) +} + +#[cfg(test)] +mod tests { + use super::require_dedicated_host_for_matrix; + + #[test] + fn single_framework_does_not_require_a_dedicated_host() { + assert!(require_dedicated_host_for_matrix("cpeak").is_ok()); + } +} diff --git a/crates/orchestrator/src/provenance.rs b/crates/orchestrator/src/provenance.rs new file mode 100644 index 0000000..7490a6f --- /dev/null +++ b/crates/orchestrator/src/provenance.rs @@ -0,0 +1,68 @@ +use crate::error::OrchestratorError; +use sha2::{Digest, Sha256}; +use std::fs; +use std::path::{Path, PathBuf}; + +pub fn framework_input_sha256(root: &Path) -> Result { + let mut files = Vec::new(); + collect_files(root, root, &mut files)?; + files.sort_unstable(); + + let mut digest = Sha256::new(); + for path in files { + let relative = path.strip_prefix(root).map_err(|error| { + OrchestratorError::Process(format!("framework input path error: {error}")) + })?; + digest.update(relative.as_os_str().as_encoded_bytes()); + digest.update([0]); + digest.update(fs::read(path)?); + } + Ok(format!("{:x}", digest.finalize())) +} + +fn collect_files( + root: &Path, + directory: &Path, + files: &mut Vec, +) -> Result<(), OrchestratorError> { + for entry in fs::read_dir(directory)? { + let entry = entry?; + let path = entry.path(); + if excluded(root, &path) { + continue; + } + if entry.file_type()?.is_dir() { + collect_files(root, &path, files)?; + } else if entry.file_type()?.is_file() { + files.push(path); + } + } + Ok(()) +} + +fn excluded(root: &Path, path: &Path) -> bool { + path.strip_prefix(root) + .ok() + .and_then(|relative| relative.components().next()) + .is_some_and(|component| matches!(component.as_os_str().to_str(), Some(".git" | "target"))) +} + +#[cfg(test)] +mod tests { + use super::framework_input_sha256; + use std::fs; + + #[test] + fn framework_digest_is_stable_and_excludes_build_outputs() { + let root = std::env::temp_dir().join(format!("provenance-{}", std::process::id())); + fs::create_dir_all(root.join("target")).unwrap(); + fs::write(root.join("framework.toml"), "name = 'test'").unwrap(); + fs::write(root.join("target/output"), "first").unwrap(); + let first = framework_input_sha256(&root).unwrap(); + fs::write(root.join("target/output"), "second").unwrap(); + assert_eq!(framework_input_sha256(&root).unwrap(), first); + fs::write(root.join("framework.toml"), "name = 'changed'").unwrap(); + assert_ne!(framework_input_sha256(&root).unwrap(), first); + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/crates/orchestrator/src/runner.rs b/crates/orchestrator/src/runner.rs index 7a56e45..5977af0 100644 --- a/crates/orchestrator/src/runner.rs +++ b/crates/orchestrator/src/runner.rs @@ -1,6 +1,7 @@ use crate::error::OrchestratorError; use crate::lifecycle::run_framework_suite; use crate::poller::wait_for_ready; +use crate::provenance::framework_input_sha256; use crate::{start_container, stop_container}; use common::manifest::FrameworkManifest; use common::result::{LibcVariant, RunProvenance, RunResult}; @@ -60,6 +61,7 @@ pub async fn execute_framework_session( config: &BenchmarkSessionConfig, ) -> Result { let revision = current_revision(framework_dir); + let framework_input_sha256 = framework_input_sha256(framework_dir)?; let target = if config.manage_container { Some(start_container( framework_dir, @@ -103,6 +105,15 @@ pub async fn execute_framework_session( .as_ref() .map(|target| target.image_id.clone()) .or_else(|| std::env::var("BENCHMARK_IMAGE_ID").ok()), + framework_input_sha256: Some(framework_input_sha256), + target_cpus: std::env::var("BENCHMARK_TARGET_CPUS").ok(), + load_cpus: std::env::var("BENCHMARK_LOAD_CPUS").ok(), + cgroup_path: target.as_ref().and_then(|target| { + target + .cgroup_dir + .as_ref() + .map(|path| path.display().to_string()) + }), }; persist_session_results(&result, &config.output_dir)?; if let Some(readme) = &config.readme_path { diff --git a/crates/reporter/src/chart.rs b/crates/reporter/src/chart.rs index c9a5d38..b994f25 100644 --- a/crates/reporter/src/chart.rs +++ b/crates/reporter/src/chart.rs @@ -1,65 +1,116 @@ use crate::logic::LeaderboardRow; use std::fmt::Write; -pub fn generate_throughput_svg(rows: &[LeaderboardRow], title: &str) -> String { - let bar_height = 28; - let gap = 16; - let top_margin = 50; - let left_margin = 160; - let chart_width = 800; - let max_bar_width = 540.0; - - let height = top_margin + (rows.len() * (bar_height + gap)) + 30; - let max_rps = rows - .iter() - .map(|r| r.requests_per_sec) - .fold(1.0_f64, f64::max); +const CHART_WIDTH: usize = 800; +const LEFT_MARGIN: usize = 180; +const MAX_BAR_WIDTH: f64 = 500.0; +const BAR_HEIGHT: usize = 24; +const GAP: usize = 12; +const PANEL_MARGIN: usize = 42; +pub fn generate_throughput_svg(rows: &[LeaderboardRow], title: &str) -> String { + let panel_height = PANEL_MARGIN + rows.len() * (BAR_HEIGHT + GAP) + GAP; + let height = 42 + panel_height * 2; let mut svg = String::new(); let _ = writeln!( svg, - r#""# + r#""# ); let _ = writeln!( svg, - r##" "## + r##""## ); let _ = writeln!( svg, - r##" {title}"## + r##"{}"##, + xml_text(title) ); + render_bars( + &mut svg, + rows, + 42, + "Throughput (requests/sec)", + throughput, + "req/s", + ); + render_bars( + &mut svg, + rows, + 42 + panel_height, + "P99 latency (ms)", + p99_latency, + "ms", + ); + svg.push_str("\n"); + svg +} - for (i, row) in rows.iter().enumerate() { - let y = top_margin + i * (bar_height + gap); - let bar_w = ((row.requests_per_sec / max_rps) * max_bar_width).max(4.0); - let label = format!("{}-{}", row.framework, row.libc); - let val_text = format!("{:.0} req/s", row.requests_per_sec); - +fn render_bars( + svg: &mut String, + rows: &[LeaderboardRow], + top: usize, + label: &str, + value: fn(&LeaderboardRow) -> f64, + unit: &str, +) { + let max_value = rows.iter().map(value).fold(1.0_f64, f64::max); + let _ = writeln!( + svg, + r##"{label}"##, + top + 16 + ); + for (index, row) in rows.iter().enumerate() { + let y = top + PANEL_MARGIN + index * (BAR_HEIGHT + GAP); + let bar_width = ((value(row) / max_value) * MAX_BAR_WIDTH).max(4.0); let color = if row.libc == "glibc" { "#38bdf8" } else { "#34d399" }; - let _ = writeln!( svg, - r##" {}"##, - y + 19, - label + r##"{}-{}"##, + y + 16, + xml_text(&row.framework), + xml_text(&row.libc) ); let _ = writeln!( svg, - r#" "# + r#""# ); let _ = writeln!( svg, - r##" {}"##, - f64::from(left_margin) + bar_w + 10.0, - y + 19, - val_text + r##"{:.3} {unit}"##, + LEFT_MARGIN as f64 + bar_width + 8.0, + y + 16, + value(row) ); } +} - svg.push_str("\n"); - svg +fn throughput(row: &LeaderboardRow) -> f64 { + row.requests_per_sec +} + +fn p99_latency(row: &LeaderboardRow) -> f64 { + row.p99_latency_ms +} + +fn xml_text(value: &str) -> String { + value + .replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) + .replace('\'', "'") +} + +#[cfg(test)] +mod tests { + use super::xml_text; + + #[test] + fn xml_text_escapes_element_and_attribute_delimiters() { + assert_eq!(xml_text("<&>\"'"), "<&>"'"); + } } diff --git a/crates/reporter/src/index.rs b/crates/reporter/src/index.rs index 0977a69..dccf4b9 100644 --- a/crates/reporter/src/index.rs +++ b/crates/reporter/src/index.rs @@ -29,15 +29,7 @@ fn historical_index_entry(result: &RunResult, relative_path: &str) -> IndexEntry } } -fn upsert_historical_index(index: &mut HistoricalIndex, entry: IndexEntry) { - if let Some(current) = index - .runs - .iter_mut() - .find(|current| current.run_id == entry.run_id) - { - *current = entry; - return; - } +fn append_historical_index(index: &mut HistoricalIndex, entry: IndexEntry) { index.runs.push(entry); } @@ -57,7 +49,7 @@ pub fn update_historical_index( HistoricalIndex::default() }; - upsert_historical_index(&mut index, historical_index_entry(result, relative_path)); + append_historical_index(&mut index, historical_index_entry(result, relative_path)); let serialized = serde_json::to_string_pretty(&index) .map_err(|e| ReporterError::Formatting(format!("failed to serialize index: {e}")))?; diff --git a/crates/reporter/src/logic.rs b/crates/reporter/src/logic.rs index 6a18970..e58add4 100644 --- a/crates/reporter/src/logic.rs +++ b/crates/reporter/src/logic.rs @@ -1,5 +1,4 @@ -use common::result::RunResult; - +use common::result::{LibcVariant, RunResult, TestMetrics}; #[derive(Debug, Clone)] pub struct LeaderboardRow { pub rank: usize, @@ -31,17 +30,25 @@ pub struct LeaderboardRow { pub http_5xx: u64, pub errors: u64, } - +#[derive(Debug, Clone, PartialEq)] +pub struct DualLibcDelta { + pub framework: String, + pub framework_version: String, + pub concurrency: u32, + pub glibc_requests_per_sec: f64, + pub musl_requests_per_sec: f64, + pub requests_per_sec_change_pct: Option, + pub glibc_p99_latency_ms: f64, + pub musl_p99_latency_ms: f64, + pub p99_latency_change_pct: Option, +} pub fn aggregate_leaderboard(results: &[RunResult], category: &str) -> Vec { let mut entries = Vec::new(); for run in results { for test in &run.benchmarks { if test.category == category { - let libc_str = match run.libc { - common::result::LibcVariant::Glibc => "glibc", - common::result::LibcVariant::Musl => "musl", - }; + let libc_str = libc_name(run.libc); entries.push(LeaderboardRow { rank: 0, framework: run.framework_name.clone(), @@ -91,26 +98,103 @@ pub fn aggregate_leaderboard(results: &[RunResult], category: &str) -> Vec Vec { + let glibc = category_metrics(results, category, LibcVariant::Glibc); + let musl = category_metrics(results, category, LibcVariant::Musl); + let mut paired = vec![false; musl.len()]; + let mut deltas = Vec::new(); -fn microseconds_to_milliseconds(value: u64) -> f64 { - (value as f64) / 1000.0 + for (glibc_run, glibc_test) in glibc { + let Some((index, (_musl_run, musl_test))) = + musl.iter().enumerate().find(|(index, item)| { + !paired[*index] && compatible_runs(glibc_run, glibc_test, item.0, item.1) + }) + else { + continue; + }; + paired[index] = true; + deltas.push(DualLibcDelta { + framework: glibc_run.framework_name.clone(), + framework_version: glibc_run.framework_version.clone(), + concurrency: glibc_test.concurrency, + glibc_requests_per_sec: glibc_test.requests_per_sec, + musl_requests_per_sec: musl_test.requests_per_sec, + requests_per_sec_change_pct: percentage_change( + glibc_test.requests_per_sec, + musl_test.requests_per_sec, + ), + glibc_p99_latency_ms: microseconds_to_milliseconds(glibc_test.latency.p99_us), + musl_p99_latency_ms: microseconds_to_milliseconds(musl_test.latency.p99_us), + p99_latency_change_pct: percentage_change( + glibc_test.latency.p99_us as f64, + musl_test.latency.p99_us as f64, + ), + }); + } + deltas.sort_by(|a, b| { + a.framework + .cmp(&b.framework) + .then_with(|| a.framework_version.cmp(&b.framework_version)) + .then_with(|| a.concurrency.cmp(&b.concurrency)) + }); + deltas } - -fn bytes_to_megabytes(value: u64) -> f64 { - (value as f64) / (1024.0 * 1024.0) +fn category_metrics<'a>( + results: &'a [RunResult], + category: &'a str, + libc: LibcVariant, +) -> Vec<(&'a RunResult, &'a TestMetrics)> { + results + .iter() + .filter(|run| run.libc == libc) + .flat_map(|run| { + run.benchmarks + .iter() + .filter(move |test| test.category == category) + .map(move |test| (run, test)) + }) + .collect() +} +fn compatible_runs( + left: &RunResult, + left_test: &TestMetrics, + right: &RunResult, + right_test: &TestMetrics, +) -> bool { + left.framework_name == right.framework_name + && left.framework_version == right.framework_version + && left.machine == right.machine + && left_test.concurrency == right_test.concurrency + && left_test.duration_secs == right_test.duration_secs + && left_test.traffic == right_test.traffic + && compatible_provenance(left, right) +} +fn compatible_provenance(left: &RunResult, right: &RunResult) -> bool { + left.provenance.revision != "unknown" + && left.provenance.revision == right.provenance.revision + && left.provenance.framework_input_sha256.is_some() + && left.provenance.framework_input_sha256 == right.provenance.framework_input_sha256 + && left.provenance.image_id.is_some() + && right.provenance.image_id.is_some() + && left.provenance.target_cpus == right.provenance.target_cpus + && left.provenance.load_cpus == right.provenance.load_cpus } -#[cfg(test)] -mod tests { - use super::{bytes_to_megabytes, microseconds_to_milliseconds}; +fn percentage_change(baseline: f64, value: f64) -> Option { + (baseline != 0.0).then(|| ((value - baseline) / baseline) * 100.0) +} - #[test] - fn microseconds_to_milliseconds_preserves_fractional_values() { - assert!((microseconds_to_milliseconds(125) - 0.125).abs() < f64::EPSILON); +fn libc_name(libc: LibcVariant) -> &'static str { + match libc { + LibcVariant::Glibc => "glibc", + LibcVariant::Musl => "musl", } +} - #[test] - fn bytes_to_megabytes_converts_binary_megabytes() { - assert!((bytes_to_megabytes(2 * 1024 * 1024) - 2.0).abs() < f64::EPSILON); - } +fn microseconds_to_milliseconds(value: u64) -> f64 { + (value as f64) / 1000.0 +} + +fn bytes_to_megabytes(value: u64) -> f64 { + (value as f64) / (1024.0 * 1024.0) } diff --git a/crates/reporter/src/markdown.rs b/crates/reporter/src/markdown.rs index 6e16d5d..b40d522 100644 --- a/crates/reporter/src/markdown.rs +++ b/crates/reporter/src/markdown.rs @@ -1,5 +1,5 @@ use crate::error::ReporterError; -use crate::logic::{LeaderboardRow, aggregate_leaderboard}; +use crate::logic::{DualLibcDelta, LeaderboardRow, aggregate_leaderboard, dual_libc_deltas}; use crate::storage::replace_file; use common::result::RunResult; use std::fmt::Write; @@ -8,16 +8,31 @@ use std::path::Path; const START_MARKER: &str = ""; const END_MARKER: &str = ""; - pub fn render_markdown_table(rows: &[LeaderboardRow], category: &str) -> String { + render_markdown_table_with_deltas(rows, &[], category) +} + +pub fn render_markdown_table_with_deltas( + rows: &[LeaderboardRow], + deltas: &[DualLibcDelta], + category: &str, +) -> String { let mut output = String::new(); let category = markdown_cell(category); let _ = writeln!(output, "### Benchmark Leaderboard: {category}\n"); + let _ = writeln!( + output, + "![{category} throughput and P99 latency](results/charts/{category}_throughput.svg)\n" + ); render_throughput_table(&mut output, rows); output.push('\n'); render_latency_table(&mut output, rows); output.push('\n'); render_resources_table(&mut output, rows); + if !deltas.is_empty() { + output.push('\n'); + render_delta_table(&mut output, deltas); + } output } @@ -35,10 +50,9 @@ pub fn render_readme_leaderboards(results: &[RunResult]) -> String { let mut section = String::from("\n\n"); for category in categories { - section.push_str(&render_markdown_table( - &aggregate_leaderboard(results, category), - category, - )); + let rows = aggregate_leaderboard(results, category); + let deltas = dual_libc_deltas(results, category); + section.push_str(&render_markdown_table_with_deltas(&rows, &deltas, category)); section.push('\n'); } section @@ -147,6 +161,27 @@ fn render_resources_table(output: &mut String, rows: &[LeaderboardRow]) { } } +fn render_delta_table(output: &mut String, deltas: &[DualLibcDelta]) { + output.push_str("#### musl vs glibc deltas\n\n"); + output.push_str("| Framework | Version | Concurrency | RPS delta | P99 delta |\n"); + output.push_str("| :-------- | :------ | ----------: | --------: | --------: |\n"); + for delta in deltas { + let _ = writeln!( + output, + "| {} | {} | {} | {} | {} |", + markdown_cell(&delta.framework), + markdown_cell(&delta.framework_version), + delta.concurrency, + percentage_cell(delta.requests_per_sec_change_pct), + percentage_cell(delta.p99_latency_change_pct), + ); + } +} + +fn percentage_cell(value: Option) -> String { + value.map_or_else(|| "n/a".into(), |value| format!("{value:+.2}%")) +} + fn markdown_cell(value: &str) -> String { value .replace('\\', "\\\\") diff --git a/crates/reporter/tests/readme_test.rs b/crates/reporter/tests/readme_test.rs index d386050..a9d9358 100644 --- a/crates/reporter/tests/readme_test.rs +++ b/crates/reporter/tests/readme_test.rs @@ -81,6 +81,7 @@ fn test_update_readme_leaderboard() { let updated = fs::read_to_string(&readme_path).expect("read updated readme failed"); assert!(updated.contains("### Benchmark Leaderboard: plaintext")); + assert!(updated.contains("results/charts/plaintext_throughput.svg")); assert!(updated.contains("axum")); assert!(updated.contains("450000.00")); diff --git a/crates/reporter/tests/reporter_test.rs b/crates/reporter/tests/reporter_test.rs index d68f396..5069fcb 100644 --- a/crates/reporter/tests/reporter_test.rs +++ b/crates/reporter/tests/reporter_test.rs @@ -7,12 +7,11 @@ use reporter::archive::{ }; use reporter::chart::generate_throughput_svg; use reporter::index::{HistoricalIndex, update_historical_index}; -use reporter::logic::aggregate_leaderboard; -use reporter::markdown::render_markdown_table; +use reporter::logic::{aggregate_leaderboard, dual_libc_deltas}; +use reporter::markdown::{render_markdown_table, render_markdown_table_with_deltas}; use std::fs; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; - fn create_dummy_run(name: &str, libc: LibcVariant, rps: f64, p99_us: u64) -> RunResult { RunResult { run_id: "test-run".into(), @@ -65,7 +64,6 @@ fn create_dummy_run(name: &str, libc: LibcVariant, rps: f64, p99_us: u64) -> Run }], } } - fn temporary_dir(label: &str) -> PathBuf { let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -78,41 +76,34 @@ fn temporary_dir(label: &str) -> PathBuf { fs::create_dir_all(&path).expect("create test directory failed"); path } - #[test] fn test_leaderboard_ranking_and_markdown() { let r1 = create_dummy_run("axum", LibcVariant::Glibc, 500_000.0, 500); let r2 = create_dummy_run("nethttp", LibcVariant::Musl, 400_000.0, 800); - let rows = aggregate_leaderboard(&[r2, r1], "plaintext"); assert_eq!(rows.len(), 2); - assert_eq!(rows[0].framework, "axum"); - assert_eq!(rows[0].rank, 1); - assert_eq!(rows[1].framework, "nethttp"); - assert_eq!(rows[1].rank, 2); - + assert_eq!((rows[0].framework.as_str(), rows[0].rank), ("axum", 1)); + assert_eq!((rows[1].framework.as_str(), rows[1].rank), ("nethttp", 2)); let markdown = render_markdown_table(&rows, "plaintext"); assert!(markdown.contains("axum")); assert!(markdown.contains("500000.00")); } - #[test] fn test_svg_chart_generation() { let r1 = create_dummy_run("axum", LibcVariant::Glibc, 500_000.0, 500); let rows = aggregate_leaderboard(&[r1], "plaintext"); let svg = generate_throughput_svg(&rows, "Plaintext Throughput"); - assert!(svg.contains("\n")); } - #[test] fn test_historical_index_update() { let temp_dir = temporary_dir("index"); let index_file = temp_dir.join("index.json"); - let r1 = create_dummy_run("axum", LibcVariant::Glibc, 500_000.0, 500); update_historical_index(&index_file, &r1, "results/data/2026/10/test-run.json") .expect("update index failed"); @@ -122,18 +113,15 @@ fn test_historical_index_update() { "results/data/2026/10/retried-test-run.json", ) .expect("repeat index update failed"); - let content = fs::read_to_string(&index_file).expect("read index failed"); let index: HistoricalIndex = serde_json::from_str(&content).expect("parse index failed"); + assert_eq!(index.runs.len(), 2, "colliding run ids must append history"); assert_eq!( - index.runs.len(), - 1, - "the same run id must not duplicate history" + index.runs[0].relative_json_path, + "results/data/2026/10/test-run.json" ); - assert_eq!(index.runs[0].framework_name, "axum"); - assert_eq!(index.runs[0].libc, "glibc"); assert_eq!( - index.runs[0].relative_json_path, + index.runs[1].relative_json_path, "results/data/2026/10/retried-test-run.json" ); @@ -184,3 +172,29 @@ fn test_timestamped_artifact_path_rejects_unsafe_run_id() { assert!(error.to_string().contains("run id")); } + +#[test] +fn test_dual_libc_deltas_require_compatible_provenance() { + let mut glibc = create_dummy_run("axum", LibcVariant::Glibc, 500_000.0, 500); + glibc.provenance = RunProvenance { + revision: "abc123".into(), + image_id: Some("glibc-image".into()), + framework_input_sha256: Some("inputs".into()), + target_cpus: Some("0-3".into()), + load_cpus: Some("4-7".into()), + ..RunProvenance::default() + }; + let mut musl = glibc.clone(); + musl.libc = LibcVariant::Musl; + musl.provenance.image_id = Some("musl-image".into()); + musl.benchmarks[0].requests_per_sec = 550_000.0; + musl.benchmarks[0].latency.p99_us = 600; + + let rows = aggregate_leaderboard(&[glibc.clone(), musl.clone()], "plaintext"); + let deltas = dual_libc_deltas(&[glibc.clone(), musl.clone()], "plaintext"); + assert_eq!(deltas[0].requests_per_sec_change_pct, Some(10.0)); + assert!(render_markdown_table_with_deltas(&rows, &deltas, "plaintext").contains("+20.00%")); + + musl.provenance.framework_input_sha256 = Some("different".into()); + assert!(dual_libc_deltas(&[glibc, musl], "plaintext").is_empty()); +} diff --git a/docs/architecture.md b/docs/architecture.md index c1549bc..abcd6cb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -18,7 +18,7 @@ ### CPU isolation - Reserve server and load-generator capacity in the host or OpenStack flavor before a run. -- The current orchestrator records a supplied cgroup path; CPU-set creation remains an operator responsibility. +- The orchestrator requires dedicated runs to declare server and load CPU sets, pins the load-generator process, and passes the server set to Docker. - Record topology and traffic placement with every publishable result. ### Cgroups v2 Resource Accounting diff --git a/docs/how_to_use.md b/docs/how_to_use.md index 905d141..ebbdd82 100644 --- a/docs/how_to_use.md +++ b/docs/how_to_use.md @@ -77,6 +77,15 @@ Each plaintext-capable adapter also runs bounded HTTP-Arena supplemental profile The compatibility entry point delegates to the all-framework runner. It validates the host, builds the release orchestrator, then lets the orchestrator build, start, measure, and remove each selected libc image. Start the repository database yourself before selecting frameworks with database capabilities. ```sh +BENCHMARK_DEDICATED_HOST=1 \ +BENCHMARK_TARGET_CPUS=2-7 \ +BENCHMARK_LOAD_CPUS=8-15 \ +BENCHMARK_CGROUP_PARENT=framework-benchmark \ +scripts/verify_host.sh --require-dedicated +BENCHMARK_DEDICATED_HOST=1 \ +BENCHMARK_TARGET_CPUS=2-7 \ +BENCHMARK_LOAD_CPUS=8-15 \ +BENCHMARK_CGROUP_PARENT=framework-benchmark \ scripts/run_benchmarks.sh ``` @@ -98,7 +107,7 @@ AUTOBAHN_TARGET_URL=ws://127.0.0.1:8080/ws scripts/run_autobahn_suite.sh ## 5. Inspect, compare, and retain results -Immutable JSON artifacts are stored under `results/data/YYYY/MM/`; the index is `results/index.json`, and generated tables/charts are in `results/`. Each artifact records the Git revision, inspected local container image ID, and per-workload traffic settings. The orchestrator replaces the README block once per selected matrix so its tables only compare results from that invocation. Review the intended result diff before staging it: +Immutable JSON artifacts are stored under `results/data/YYYY/MM/`; the index is `results/index.json`, and generated tables/charts are in `results/`. Each artifact records the Git revision, immutable framework-input SHA-256, inspected local image ID, CPU placement, cgroup path, and per-workload traffic settings. The orchestrator replaces the README block once per selected matrix so its tables only compare results from that invocation. Review the intended result diff before staging it: ```sh find results -type f | sort diff --git a/docs/roadmap/phase5_deployment.md b/docs/roadmap/phase5_deployment.md index a424d38..07d4814 100644 --- a/docs/roadmap/phase5_deployment.md +++ b/docs/roadmap/phase5_deployment.md @@ -18,6 +18,7 @@ Phase 5 automates host tuning, bare-metal runner scheduling, and GitHub pull req - [x] Test execution (`cargo test --workspace`). ### 5.3 Automated Benchmark Runner Lifecycle -- [ ] Run the complete matrix only on a labelled dedicated host; hosted CI is not a defensible performance environment. +- [x] Enforce that complete matrices run only on a labelled dedicated host; hosted CI runs a deterministic dual-libc smoke target instead. +- [ ] Produce and review a complete dual-libc matrix on the designated host before publishing results. - [x] Implement runner orchestrator execution script (`scripts/run_benchmarks.sh`) and CLI (`crates/orchestrator/src/main.rs`). - [x] Maintain automated output generation for Markdown tables and SVG charts under `results/`. diff --git a/docs/schemas/result_schema.json b/docs/schemas/result_schema.json index 90971e8..47c3272 100644 --- a/docs/schemas/result_schema.json +++ b/docs/schemas/result_schema.json @@ -37,7 +37,11 @@ "required": ["revision", "image_id"], "properties": { "revision": { "type": "string", "minLength": 1 }, - "image_id": { "type": ["string", "null"] } + "image_id": { "type": ["string", "null"] }, + "framework_input_sha256": { "type": ["string", "null"] }, + "target_cpus": { "type": ["string", "null"] }, + "load_cpus": { "type": ["string", "null"] }, + "cgroup_path": { "type": ["string", "null"] } } }, "benchmarks": { diff --git a/flake.nix b/flake.nix index 7e3c02b..6407caf 100644 --- a/flake.nix +++ b/flake.nix @@ -17,8 +17,6 @@ systems = [ "x86_64-linux" "aarch64-linux" - "aarch64-darwin" - "x86_64-darwin" ]; perSystem = { @@ -84,7 +82,14 @@ else "default"; }; in { - packages.default = mkPackage pkgs; + packages = + { + default = mkPackage pkgs; + } + // lib.optionalAttrs isLinux { + server-glibc = mkPackage pkgs; + server-musl = mkPackage pkgsMusl; + }; devShells = { diff --git a/frameworks/web-server/cpp/agensio/Dockerfile.glibc b/frameworks/web-server/cpp/agensio/Dockerfile.glibc index a3c6031..9e835f3 100644 --- a/frameworks/web-server/cpp/agensio/Dockerfile.glibc +++ b/frameworks/web-server/cpp/agensio/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM debian:trixie AS build +FROM debian:trixie@sha256:913f6706df59a68922d1dd08f78c2476560a8d367897200a6005b00e5f67c2d5 AS build WORKDIR /src RUN apt-get update -qq && apt-get install -y --no-install-recommends \ build-essential ca-certificates cmake git libasio-dev libssl-dev ninja-build \ @@ -10,7 +10,7 @@ RUN git init /src/agensio && git -C /src/agensio remote add origin \ RUN cmake -S /src/agensio -B /src/build -G Ninja -DCMAKE_BUILD_TYPE=Release \ -DAGENSIO_NATIVE=ON -DAGENSIO_TESTS=OFF && cmake --build /src/build --target agensio -FROM debian:trixie-slim +FROM debian:trixie-slim@sha256:a29215f6a35e51e22adffa17f89e9d2ef06214e64a2bad10d765c46aea49f11f WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends \ libssl3t64 zlib1g && rm -rf /var/lib/apt/lists/* diff --git a/frameworks/web-server/cpp/agensio/Dockerfile.musl b/frameworks/web-server/cpp/agensio/Dockerfile.musl index eef8450..9930bd4 100644 --- a/frameworks/web-server/cpp/agensio/Dockerfile.musl +++ b/frameworks/web-server/cpp/agensio/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM alpine:3.21 AS build +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 AS build WORKDIR /src RUN apk add --no-cache asio-dev cmake g++ git make ninja openssl-dev zlib-dev RUN git init /src/agensio && git -C /src/agensio remote add origin \ @@ -8,7 +8,7 @@ RUN git init /src/agensio && git -C /src/agensio remote add origin \ RUN cmake -S /src/agensio -B /src/build -G Ninja -DCMAKE_BUILD_TYPE=Release \ -DAGENSIO_NATIVE=ON -DAGENSIO_TESTS=OFF && cmake --build /src/build --target agensio -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache libstdc++ openssl zlib COPY agensio.toml /etc/agensio/agensio.toml diff --git a/frameworks/web-server/cpp/drogon/Dockerfile.glibc b/frameworks/web-server/cpp/drogon/Dockerfile.glibc index 0c18731..9a3efc9 100644 --- a/frameworks/web-server/cpp/drogon/Dockerfile.glibc +++ b/frameworks/web-server/cpp/drogon/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim AS build +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 AS build WORKDIR /src RUN apt-get update -qq && apt-get install -y --no-install-recommends \ cmake g++ git libjsoncpp-dev make uuid-dev zlib1g-dev && \ @@ -13,7 +13,7 @@ COPY CMakeLists.txt main.cpp ./ RUN cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/opt/drogon/install && \ cmake --build build --parallel -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends \ libjsoncpp25 libuuid1 zlib1g && rm -rf /var/lib/apt/lists/* diff --git a/frameworks/web-server/cpp/drogon/Dockerfile.musl b/frameworks/web-server/cpp/drogon/Dockerfile.musl index 7e249de..6c28233 100644 --- a/frameworks/web-server/cpp/drogon/Dockerfile.musl +++ b/frameworks/web-server/cpp/drogon/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM alpine:3.21 AS build +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 AS build WORKDIR /src RUN apk add --no-cache cmake g++ git jsoncpp-dev make util-linux-dev zlib-dev RUN git clone --depth 1 --branch v1.9.8 --recurse-submodules \ @@ -11,7 +11,7 @@ COPY CMakeLists.txt main.cpp ./ RUN cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/opt/drogon/install && \ cmake --build build --parallel -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache jsoncpp libstdc++ libuuid zlib COPY --from=build /src/build/server ./server diff --git a/frameworks/web-server/cpp/hical/Dockerfile.glibc b/frameworks/web-server/cpp/hical/Dockerfile.glibc index c1c72f9..45ec851 100644 --- a/frameworks/web-server/cpp/hical/Dockerfile.glibc +++ b/frameworks/web-server/cpp/hical/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM ubuntu:24.04 AS build +FROM ubuntu:24.04@sha256:534baea6a22c03a63003dbc8dbe78fe34bc0d7e595d9a9dc9834884ff530eb55 AS build WORKDIR /src RUN apt-get update -qq && apt-get install -y --no-install-recommends \ cmake g++ git libboost-json1.83-dev libssl-dev make zlib1g-dev && \ @@ -10,7 +10,7 @@ RUN git init /opt/hical && git -C /opt/hical remote add origin \ COPY CMakeLists.txt main.cpp ./ RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel -FROM ubuntu:24.04 +FROM ubuntu:24.04@sha256:534baea6a22c03a63003dbc8dbe78fe34bc0d7e595d9a9dc9834884ff530eb55 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends \ libboost-json1.83.0 libssl3 zlib1g && rm -rf /var/lib/apt/lists/* diff --git a/frameworks/web-server/cpp/hical/Dockerfile.musl b/frameworks/web-server/cpp/hical/Dockerfile.musl index c16d715..0af43a3 100644 --- a/frameworks/web-server/cpp/hical/Dockerfile.musl +++ b/frameworks/web-server/cpp/hical/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM alpine:3.21 AS build +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 AS build WORKDIR /src RUN apk add --no-cache boost-dev cmake g++ git make openssl-dev zlib-dev RUN git init /opt/hical && git -C /opt/hical remote add origin \ @@ -8,7 +8,7 @@ RUN git init /opt/hical && git -C /opt/hical remote add origin \ COPY CMakeLists.txt main.cpp ./ RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache boost-json libstdc++ openssl zlib COPY --from=build /src/build/server /app/server diff --git a/frameworks/web-server/cpp/uwebsockets/Dockerfile.glibc b/frameworks/web-server/cpp/uwebsockets/Dockerfile.glibc index a7d2850..93a00a6 100644 --- a/frameworks/web-server/cpp/uwebsockets/Dockerfile.glibc +++ b/frameworks/web-server/cpp/uwebsockets/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim AS build +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 AS build WORKDIR /src RUN apt-get update -qq && apt-get install -y --no-install-recommends \ cmake g++ git make && rm -rf /var/lib/apt/lists/* @@ -8,7 +8,7 @@ RUN git clone --depth 1 --branch v20.69.0 --recurse-submodules \ COPY CMakeLists.txt main.cpp ./ RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends \ libstdc++6 && rm -rf /var/lib/apt/lists/* diff --git a/frameworks/web-server/cpp/uwebsockets/Dockerfile.musl b/frameworks/web-server/cpp/uwebsockets/Dockerfile.musl index dcfe532..c7857ef 100644 --- a/frameworks/web-server/cpp/uwebsockets/Dockerfile.musl +++ b/frameworks/web-server/cpp/uwebsockets/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM alpine:3.21 AS build +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 AS build WORKDIR /src RUN apk add --no-cache cmake g++ git make RUN git clone --depth 1 --branch v20.69.0 --recurse-submodules \ @@ -7,7 +7,7 @@ RUN git clone --depth 1 --branch v20.69.0 --recurse-submodules \ COPY CMakeLists.txt main.cpp ./ RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache libstdc++ COPY --from=build /src/build/server ./server diff --git a/frameworks/web-server/csharp/aspnet-minimal/Dockerfile.glibc b/frameworks/web-server/csharp/aspnet-minimal/Dockerfile.glibc index 6091eef..c48f642 100644 --- a/frameworks/web-server/csharp/aspnet-minimal/Dockerfile.glibc +++ b/frameworks/web-server/csharp/aspnet-minimal/Dockerfile.glibc @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:4e5864c8e21b6005d3445b056f92541c2d145499b760e89d5e012ee4f0459331 AS build WORKDIR /src COPY aspnet-minimal.csproj ./ RUN dotnet restore COPY Program.cs ./ RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false -FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS final +FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim@sha256:0712631f86d30f5290544fd97d37bd281e44aed8ebe1ba436f2ddb1dd0d4983f AS final WORKDIR /app COPY --from=build /app/publish . EXPOSE 8080 diff --git a/frameworks/web-server/csharp/aspnet-minimal/Dockerfile.musl b/frameworks/web-server/csharp/aspnet-minimal/Dockerfile.musl index 2e48d0e..4229ae0 100644 --- a/frameworks/web-server/csharp/aspnet-minimal/Dockerfile.musl +++ b/frameworks/web-server/csharp/aspnet-minimal/Dockerfile.musl @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine@sha256:8e4eb048fecbd865e94fa6f0c65b592a4e783708003d12ff7097d68751817923 AS build WORKDIR /src COPY aspnet-minimal.csproj ./ RUN dotnet restore COPY Program.cs ./ RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false -FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final +FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine@sha256:cf6fd09eff16d61bd7894751a652ae531800b1e0006a0b5514180afce54a0317 AS final WORKDIR /app COPY --from=build /app/publish . EXPOSE 8080 diff --git a/frameworks/web-server/csharp/cadente/Dockerfile.glibc b/frameworks/web-server/csharp/cadente/Dockerfile.glibc index 041992e..a3968e2 100644 --- a/frameworks/web-server/csharp/cadente/Dockerfile.glibc +++ b/frameworks/web-server/csharp/cadente/Dockerfile.glibc @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:4e5864c8e21b6005d3445b056f92541c2d145499b760e89d5e012ee4f0459331 AS build WORKDIR /src COPY cadente.csproj ./ RUN dotnet restore COPY Program.cs ./ RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false -FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS final +FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim@sha256:0712631f86d30f5290544fd97d37bd281e44aed8ebe1ba436f2ddb1dd0d4983f AS final WORKDIR /app COPY --from=build /app/publish . EXPOSE 8080 diff --git a/frameworks/web-server/csharp/cadente/Dockerfile.musl b/frameworks/web-server/csharp/cadente/Dockerfile.musl index ed0146e..2d615f5 100644 --- a/frameworks/web-server/csharp/cadente/Dockerfile.musl +++ b/frameworks/web-server/csharp/cadente/Dockerfile.musl @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine@sha256:8e4eb048fecbd865e94fa6f0c65b592a4e783708003d12ff7097d68751817923 AS build WORKDIR /src COPY cadente.csproj ./ RUN dotnet restore COPY Program.cs ./ RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false -FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final +FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine@sha256:cf6fd09eff16d61bd7894751a652ae531800b1e0006a0b5514180afce54a0317 AS final WORKDIR /app COPY --from=build /app/publish . EXPOSE 8080 diff --git a/frameworks/web-server/csharp/genhttp/Dockerfile.glibc b/frameworks/web-server/csharp/genhttp/Dockerfile.glibc index 5e9f552..1c68a2c 100644 --- a/frameworks/web-server/csharp/genhttp/Dockerfile.glibc +++ b/frameworks/web-server/csharp/genhttp/Dockerfile.glibc @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:4e5864c8e21b6005d3445b056f92541c2d145499b760e89d5e012ee4f0459331 AS build WORKDIR /src COPY genhttp.csproj ./ RUN dotnet restore COPY Program.cs ./ RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false -FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS final +FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim@sha256:0712631f86d30f5290544fd97d37bd281e44aed8ebe1ba436f2ddb1dd0d4983f AS final WORKDIR /app COPY --from=build /app/publish . EXPOSE 8080 diff --git a/frameworks/web-server/csharp/genhttp/Dockerfile.musl b/frameworks/web-server/csharp/genhttp/Dockerfile.musl index 69fd380..c35813a 100644 --- a/frameworks/web-server/csharp/genhttp/Dockerfile.musl +++ b/frameworks/web-server/csharp/genhttp/Dockerfile.musl @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine@sha256:8e4eb048fecbd865e94fa6f0c65b592a4e783708003d12ff7097d68751817923 AS build WORKDIR /src COPY genhttp.csproj ./ RUN dotnet restore COPY Program.cs ./ RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false -FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final +FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine@sha256:cf6fd09eff16d61bd7894751a652ae531800b1e0006a0b5514180afce54a0317 AS final WORKDIR /app COPY --from=build /app/publish . EXPOSE 8080 diff --git a/frameworks/web-server/go/fasthttp/Dockerfile.glibc b/frameworks/web-server/go/fasthttp/Dockerfile.glibc index 5d693c8..0ce104d 100644 --- a/frameworks/web-server/go/fasthttp/Dockerfile.glibc +++ b/frameworks/web-server/go/fasthttp/Dockerfile.glibc @@ -1,11 +1,11 @@ -FROM golang:1.23-bookworm AS builder +FROM golang:1.23-bookworm@sha256:167053a2bb901972bf2c1611f8f52c44d5fe7e762e5cab213708d82c421614db AS builder WORKDIR /usr/src/app COPY go.mod ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server main.go -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/server /app/server diff --git a/frameworks/web-server/go/fasthttp/Dockerfile.musl b/frameworks/web-server/go/fasthttp/Dockerfile.musl index 667bf39..4aec305 100644 --- a/frameworks/web-server/go/fasthttp/Dockerfile.musl +++ b/frameworks/web-server/go/fasthttp/Dockerfile.musl @@ -1,11 +1,11 @@ -FROM golang:1.23-alpine3.21 AS builder +FROM golang:1.23-alpine3.21@sha256:4bb4be21ac98da06bc26437ee870c4973f8039f13e9a1a36971b4517632b0fc6 AS builder WORKDIR /usr/src/app COPY go.mod ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server main.go -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache ca-certificates curl COPY --from=builder /app/server /app/server diff --git a/frameworks/web-server/go/fib-tuned/Dockerfile.glibc b/frameworks/web-server/go/fib-tuned/Dockerfile.glibc index 5d693c8..0ce104d 100644 --- a/frameworks/web-server/go/fib-tuned/Dockerfile.glibc +++ b/frameworks/web-server/go/fib-tuned/Dockerfile.glibc @@ -1,11 +1,11 @@ -FROM golang:1.23-bookworm AS builder +FROM golang:1.23-bookworm@sha256:167053a2bb901972bf2c1611f8f52c44d5fe7e762e5cab213708d82c421614db AS builder WORKDIR /usr/src/app COPY go.mod ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server main.go -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/server /app/server diff --git a/frameworks/web-server/go/fib-tuned/Dockerfile.musl b/frameworks/web-server/go/fib-tuned/Dockerfile.musl index 667bf39..4aec305 100644 --- a/frameworks/web-server/go/fib-tuned/Dockerfile.musl +++ b/frameworks/web-server/go/fib-tuned/Dockerfile.musl @@ -1,11 +1,11 @@ -FROM golang:1.23-alpine3.21 AS builder +FROM golang:1.23-alpine3.21@sha256:4bb4be21ac98da06bc26437ee870c4973f8039f13e9a1a36971b4517632b0fc6 AS builder WORKDIR /usr/src/app COPY go.mod ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server main.go -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache ca-certificates curl COPY --from=builder /app/server /app/server diff --git a/frameworks/web-server/go/fiber/Dockerfile.glibc b/frameworks/web-server/go/fiber/Dockerfile.glibc index 5d693c8..0ce104d 100644 --- a/frameworks/web-server/go/fiber/Dockerfile.glibc +++ b/frameworks/web-server/go/fiber/Dockerfile.glibc @@ -1,11 +1,11 @@ -FROM golang:1.23-bookworm AS builder +FROM golang:1.23-bookworm@sha256:167053a2bb901972bf2c1611f8f52c44d5fe7e762e5cab213708d82c421614db AS builder WORKDIR /usr/src/app COPY go.mod ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server main.go -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/server /app/server diff --git a/frameworks/web-server/go/fiber/Dockerfile.musl b/frameworks/web-server/go/fiber/Dockerfile.musl index 667bf39..4aec305 100644 --- a/frameworks/web-server/go/fiber/Dockerfile.musl +++ b/frameworks/web-server/go/fiber/Dockerfile.musl @@ -1,11 +1,11 @@ -FROM golang:1.23-alpine3.21 AS builder +FROM golang:1.23-alpine3.21@sha256:4bb4be21ac98da06bc26437ee870c4973f8039f13e9a1a36971b4517632b0fc6 AS builder WORKDIR /usr/src/app COPY go.mod ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server main.go -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache ca-certificates curl COPY --from=builder /app/server /app/server diff --git a/frameworks/web-server/java/rapidoid/Dockerfile.glibc b/frameworks/web-server/java/rapidoid/Dockerfile.glibc index 506f301..f01f5f3 100644 --- a/frameworks/web-server/java/rapidoid/Dockerfile.glibc +++ b/frameworks/web-server/java/rapidoid/Dockerfile.glibc @@ -1,10 +1,10 @@ -FROM maven:3.9-eclipse-temurin-21-jammy AS build +FROM maven:3.9-eclipse-temurin-21-jammy@sha256:7f8422bd81f19b9035007a75e2f82272b09af64db08848d7d4e93302466e8b6f AS build WORKDIR /src COPY pom.xml ./ COPY Main.java ./src/main/java/Main.java RUN mvn clean package -DskipTests -FROM eclipse-temurin:21-jre-jammy +FROM eclipse-temurin:21-jre-jammy@sha256:f04fb34e053148344e83317976114ec3f37e4b830ec8bdab5a2fe3cecd7d010b WORKDIR /app COPY --from=build /src/target/rapidoid-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 diff --git a/frameworks/web-server/java/rapidoid/Dockerfile.musl b/frameworks/web-server/java/rapidoid/Dockerfile.musl index 90e650b..b1eb290 100644 --- a/frameworks/web-server/java/rapidoid/Dockerfile.musl +++ b/frameworks/web-server/java/rapidoid/Dockerfile.musl @@ -1,10 +1,10 @@ -FROM maven:3.9-eclipse-temurin-21-alpine AS build +FROM maven:3.9-eclipse-temurin-21-alpine@sha256:308cba8b638ed7e4658cea3f8399066219466211c805f6d5728c3c9c7614661b AS build WORKDIR /src COPY pom.xml ./ COPY Main.java ./src/main/java/Main.java RUN mvn clean package -DskipTests -FROM eclipse-temurin:21-jre-alpine +FROM eclipse-temurin:21-jre-alpine@sha256:51ab5e3302e7141ce665ca3ea85e8b5cd648eafbc3c0c90dd79d6537684e4555 WORKDIR /app COPY --from=build /src/target/rapidoid-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 diff --git a/frameworks/web-server/java/undertow/Dockerfile.glibc b/frameworks/web-server/java/undertow/Dockerfile.glibc index f5f0f8b..68b1423 100644 --- a/frameworks/web-server/java/undertow/Dockerfile.glibc +++ b/frameworks/web-server/java/undertow/Dockerfile.glibc @@ -1,10 +1,10 @@ -FROM maven:3.9-eclipse-temurin-21-jammy AS build +FROM maven:3.9-eclipse-temurin-21-jammy@sha256:7f8422bd81f19b9035007a75e2f82272b09af64db08848d7d4e93302466e8b6f AS build WORKDIR /src COPY pom.xml ./ COPY Main.java ./src/main/java/Main.java RUN mvn clean package -DskipTests -FROM eclipse-temurin:21-jre-jammy +FROM eclipse-temurin:21-jre-jammy@sha256:f04fb34e053148344e83317976114ec3f37e4b830ec8bdab5a2fe3cecd7d010b WORKDIR /app COPY --from=build /src/target/undertow-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 diff --git a/frameworks/web-server/java/undertow/Dockerfile.musl b/frameworks/web-server/java/undertow/Dockerfile.musl index 48ac83f..e0af8b4 100644 --- a/frameworks/web-server/java/undertow/Dockerfile.musl +++ b/frameworks/web-server/java/undertow/Dockerfile.musl @@ -1,10 +1,10 @@ -FROM maven:3.9-eclipse-temurin-21-alpine AS build +FROM maven:3.9-eclipse-temurin-21-alpine@sha256:308cba8b638ed7e4658cea3f8399066219466211c805f6d5728c3c9c7614661b AS build WORKDIR /src COPY pom.xml ./ COPY Main.java ./src/main/java/Main.java RUN mvn clean package -DskipTests -FROM eclipse-temurin:21-jre-alpine +FROM eclipse-temurin:21-jre-alpine@sha256:51ab5e3302e7141ce665ca3ea85e8b5cd648eafbc3c0c90dd79d6537684e4555 WORKDIR /app COPY --from=build /src/target/undertow-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 diff --git a/frameworks/web-server/java/vertx/Dockerfile.glibc b/frameworks/web-server/java/vertx/Dockerfile.glibc index b54a556..90aa9c3 100644 --- a/frameworks/web-server/java/vertx/Dockerfile.glibc +++ b/frameworks/web-server/java/vertx/Dockerfile.glibc @@ -1,10 +1,10 @@ -FROM maven:3.9-eclipse-temurin-21-jammy AS build +FROM maven:3.9-eclipse-temurin-21-jammy@sha256:7f8422bd81f19b9035007a75e2f82272b09af64db08848d7d4e93302466e8b6f AS build WORKDIR /src COPY pom.xml ./ COPY Main.java ./src/main/java/Main.java RUN mvn clean package -DskipTests -FROM eclipse-temurin:21-jre-jammy +FROM eclipse-temurin:21-jre-jammy@sha256:f04fb34e053148344e83317976114ec3f37e4b830ec8bdab5a2fe3cecd7d010b WORKDIR /app COPY --from=build /src/target/vertx-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 diff --git a/frameworks/web-server/java/vertx/Dockerfile.musl b/frameworks/web-server/java/vertx/Dockerfile.musl index bd2df7c..e6ce8cc 100644 --- a/frameworks/web-server/java/vertx/Dockerfile.musl +++ b/frameworks/web-server/java/vertx/Dockerfile.musl @@ -1,10 +1,10 @@ -FROM maven:3.9-eclipse-temurin-21-alpine AS build +FROM maven:3.9-eclipse-temurin-21-alpine@sha256:308cba8b638ed7e4658cea3f8399066219466211c805f6d5728c3c9c7614661b AS build WORKDIR /src COPY pom.xml ./ COPY Main.java ./src/main/java/Main.java RUN mvn clean package -DskipTests -FROM eclipse-temurin:21-jre-alpine +FROM eclipse-temurin:21-jre-alpine@sha256:51ab5e3302e7141ce665ca3ea85e8b5cd648eafbc3c0c90dd79d6537684e4555 WORKDIR /app COPY --from=build /src/target/vertx-benchmark-0.1.0.jar /app/app.jar EXPOSE 8080 diff --git a/frameworks/web-server/javascript/bun/Dockerfile.glibc b/frameworks/web-server/javascript/bun/Dockerfile.glibc index f649ed8..1c2e4ae 100644 --- a/frameworks/web-server/javascript/bun/Dockerfile.glibc +++ b/frameworks/web-server/javascript/bun/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM oven/bun:1.3.3-slim +FROM oven/bun:1.3.3-slim@sha256:5d55d9702e1c634a931f048d0ec84d35583450d6059327cb88d82edd55068556 WORKDIR /app COPY package.json server.ts ./ EXPOSE 8080 diff --git a/frameworks/web-server/javascript/bun/Dockerfile.musl b/frameworks/web-server/javascript/bun/Dockerfile.musl index 74cbdf6..484bdb0 100644 --- a/frameworks/web-server/javascript/bun/Dockerfile.musl +++ b/frameworks/web-server/javascript/bun/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM oven/bun:1.3.3-alpine +FROM oven/bun:1.3.3-alpine@sha256:d2bc1fbc3afcd3d70afc2bb2544235bf559caae2a3084e9abed126e233797511 WORKDIR /app COPY package.json server.ts ./ EXPOSE 8080 diff --git a/frameworks/web-server/javascript/elysia/Dockerfile.glibc b/frameworks/web-server/javascript/elysia/Dockerfile.glibc index 54ab07f..7b9046a 100644 --- a/frameworks/web-server/javascript/elysia/Dockerfile.glibc +++ b/frameworks/web-server/javascript/elysia/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM oven/bun:1.3.3-slim +FROM oven/bun:1.3.3-slim@sha256:5d55d9702e1c634a931f048d0ec84d35583450d6059327cb88d82edd55068556 WORKDIR /app COPY package.json ./ RUN bun install --production diff --git a/frameworks/web-server/javascript/elysia/Dockerfile.musl b/frameworks/web-server/javascript/elysia/Dockerfile.musl index 8f34647..724d01e 100644 --- a/frameworks/web-server/javascript/elysia/Dockerfile.musl +++ b/frameworks/web-server/javascript/elysia/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM oven/bun:1.3.3-alpine +FROM oven/bun:1.3.3-alpine@sha256:d2bc1fbc3afcd3d70afc2bb2544235bf559caae2a3084e9abed126e233797511 WORKDIR /app COPY package.json ./ RUN bun install --production diff --git a/frameworks/web-server/javascript/fulmine/Dockerfile.glibc b/frameworks/web-server/javascript/fulmine/Dockerfile.glibc index de1adc6..fd00590 100644 --- a/frameworks/web-server/javascript/fulmine/Dockerfile.glibc +++ b/frameworks/web-server/javascript/fulmine/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM node:22-trixie-slim +FROM node:22-trixie-slim@sha256:154ba2f4d6fec323d28e4f4bb86bba4677f1223391a1979cf521304e03a98dfa WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates git && rm -rf /var/lib/apt/lists/* RUN git config --global url."https://github.com/".insteadOf ssh://git@github.com/ diff --git a/frameworks/web-server/javascript/fulmine/Dockerfile.musl b/frameworks/web-server/javascript/fulmine/Dockerfile.musl index 6c30239..8040ab0 100644 --- a/frameworks/web-server/javascript/fulmine/Dockerfile.musl +++ b/frameworks/web-server/javascript/fulmine/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM node:22-alpine3.21 +FROM node:22-alpine3.21@sha256:af8023ec879993821f6d5b21382ed915622a1b0f1cc03dbeb6804afaf01f8885 WORKDIR /app RUN apk add --no-cache git RUN git config --global url."https://github.com/".insteadOf ssh://git@github.com/ diff --git a/frameworks/web-server/php/frankenphp/Dockerfile.glibc b/frameworks/web-server/php/frankenphp/Dockerfile.glibc index 659e3f8..0593f8e 100644 --- a/frameworks/web-server/php/frankenphp/Dockerfile.glibc +++ b/frameworks/web-server/php/frankenphp/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM dunglas/frankenphp:latest-bookworm +FROM dunglas/frankenphp:latest-bookworm@sha256:97fce37efebd6678013ecc3751e17af8b877cc8b59b7f31a0e7b8dffb0066fa7 WORKDIR /app COPY server.php /app/worker.php EXPOSE 8080 diff --git a/frameworks/web-server/php/frankenphp/Dockerfile.musl b/frameworks/web-server/php/frankenphp/Dockerfile.musl index fa996a2..3d329b3 100644 --- a/frameworks/web-server/php/frankenphp/Dockerfile.musl +++ b/frameworks/web-server/php/frankenphp/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM dunglas/frankenphp:latest-alpine +FROM dunglas/frankenphp:latest-alpine@sha256:5bd857d2d22dc41f1e4c332f7f5cfc11a62b3ab9ebe4ec9f012c32f47acc47aa WORKDIR /app COPY server.php /app/worker.php EXPOSE 8080 diff --git a/frameworks/web-server/php/swoole/Dockerfile.glibc b/frameworks/web-server/php/swoole/Dockerfile.glibc index 1c74b88..4b0fa51 100644 --- a/frameworks/web-server/php/swoole/Dockerfile.glibc +++ b/frameworks/web-server/php/swoole/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM php:8.3-cli-bookworm +FROM php:8.3-cli-bookworm@sha256:c24b55afdf860c874b9ec6267ced132ff52de97d308ab08e6cf41e8596e509f7 RUN apt-get update -qq && apt-get install -y --no-install-recommends libssl-dev libcurl4-openssl-dev && rm -rf /var/lib/apt/lists/* RUN pecl install swoole && docker-php-ext-enable swoole WORKDIR /app diff --git a/frameworks/web-server/php/swoole/Dockerfile.musl b/frameworks/web-server/php/swoole/Dockerfile.musl index 617dd31..f580c1c 100644 --- a/frameworks/web-server/php/swoole/Dockerfile.musl +++ b/frameworks/web-server/php/swoole/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM php:8.3-cli-alpine3.21 +FROM php:8.3-cli-alpine3.21@sha256:f0d5ab7d92c640584a6d885beed40b50764a2d4ded02ec26be144cc238db61db RUN apk add --no-cache autoconf gcc g++ make linux-headers openssl-dev RUN pecl install swoole && docker-php-ext-enable swoole WORKDIR /app diff --git a/frameworks/web-server/php/workerman/Dockerfile.glibc b/frameworks/web-server/php/workerman/Dockerfile.glibc index 524f9bf..5ba6d8e 100644 --- a/frameworks/web-server/php/workerman/Dockerfile.glibc +++ b/frameworks/web-server/php/workerman/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM php:8.3-cli-bookworm +FROM php:8.3-cli-bookworm@sha256:c24b55afdf860c874b9ec6267ced132ff52de97d308ab08e6cf41e8596e509f7 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends git unzip && rm -rf /var/lib/apt/lists/* RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer diff --git a/frameworks/web-server/php/workerman/Dockerfile.musl b/frameworks/web-server/php/workerman/Dockerfile.musl index 4fc6d50..3e45c13 100644 --- a/frameworks/web-server/php/workerman/Dockerfile.musl +++ b/frameworks/web-server/php/workerman/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM php:8.3-cli-alpine3.21 +FROM php:8.3-cli-alpine3.21@sha256:f0d5ab7d92c640584a6d885beed40b50764a2d4ded02ec26be144cc238db61db WORKDIR /app RUN apk add --no-cache git unzip RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer diff --git a/frameworks/web-server/python/blacksheep/Dockerfile.glibc b/frameworks/web-server/python/blacksheep/Dockerfile.glibc index e2b8a98..7abd34a 100644 --- a/frameworks/web-server/python/blacksheep/Dockerfile.glibc +++ b/frameworks/web-server/python/blacksheep/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM python:3.12-slim-bookworm +FROM python:3.12-slim-bookworm@sha256:34386ef0cb081344d7ec1c103ba398e6e9f64e9ab3a1509accc92a4e24a07258 WORKDIR /app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt diff --git a/frameworks/web-server/python/blacksheep/Dockerfile.musl b/frameworks/web-server/python/blacksheep/Dockerfile.musl index 1e0c5b0..17015c8 100644 --- a/frameworks/web-server/python/blacksheep/Dockerfile.musl +++ b/frameworks/web-server/python/blacksheep/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM python:3.12-alpine3.21 +FROM python:3.12-alpine3.21@sha256:d4f9227f21409479c7fe92288f2e40b1a56ae591c8ad3b5446dfeaef90f67857 WORKDIR /app RUN apk add --no-cache gcc musl-dev linux-headers COPY requirements.txt ./ diff --git a/frameworks/web-server/python/granian/Dockerfile.glibc b/frameworks/web-server/python/granian/Dockerfile.glibc index 4e2819f..32b180b 100644 --- a/frameworks/web-server/python/granian/Dockerfile.glibc +++ b/frameworks/web-server/python/granian/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM python:3.12-slim-bookworm +FROM python:3.12-slim-bookworm@sha256:34386ef0cb081344d7ec1c103ba398e6e9f64e9ab3a1509accc92a4e24a07258 WORKDIR /app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt diff --git a/frameworks/web-server/python/granian/Dockerfile.musl b/frameworks/web-server/python/granian/Dockerfile.musl index e43e295..2b81156 100644 --- a/frameworks/web-server/python/granian/Dockerfile.musl +++ b/frameworks/web-server/python/granian/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM python:3.12-alpine3.21 +FROM python:3.12-alpine3.21@sha256:d4f9227f21409479c7fe92288f2e40b1a56ae591c8ad3b5446dfeaef90f67857 WORKDIR /app RUN apk add --no-cache gcc musl-dev linux-headers COPY requirements.txt ./ diff --git a/frameworks/web-server/python/robyn/Dockerfile.glibc b/frameworks/web-server/python/robyn/Dockerfile.glibc index cec0fcb..b3178b8 100644 --- a/frameworks/web-server/python/robyn/Dockerfile.glibc +++ b/frameworks/web-server/python/robyn/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM python:3.12-slim-bookworm +FROM python:3.12-slim-bookworm@sha256:34386ef0cb081344d7ec1c103ba398e6e9f64e9ab3a1509accc92a4e24a07258 WORKDIR /app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt diff --git a/frameworks/web-server/python/robyn/Dockerfile.musl b/frameworks/web-server/python/robyn/Dockerfile.musl index 5632426..305fe57 100644 --- a/frameworks/web-server/python/robyn/Dockerfile.musl +++ b/frameworks/web-server/python/robyn/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM python:3.12-alpine3.21 +FROM python:3.12-alpine3.21@sha256:d4f9227f21409479c7fe92288f2e40b1a56ae591c8ad3b5446dfeaef90f67857 WORKDIR /app RUN apk add --no-cache gcc musl-dev linux-headers COPY requirements.txt ./ diff --git a/frameworks/web-server/ruby/agoo/Dockerfile.glibc b/frameworks/web-server/ruby/agoo/Dockerfile.glibc index 6135db8..847a023 100644 --- a/frameworks/web-server/ruby/agoo/Dockerfile.glibc +++ b/frameworks/web-server/ruby/agoo/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM ruby:3.3-bookworm +FROM ruby:3.3-bookworm@sha256:b899e7de6ffa6e974d947202d3ce2ac921c12e46c14cff3106214079388dcff4 WORKDIR /app ENV RUBY_YJIT_ENABLE=1 RUN apt-get update -qq && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/* diff --git a/frameworks/web-server/ruby/agoo/Dockerfile.musl b/frameworks/web-server/ruby/agoo/Dockerfile.musl index 171d268..de09eb7 100644 --- a/frameworks/web-server/ruby/agoo/Dockerfile.musl +++ b/frameworks/web-server/ruby/agoo/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM ruby:3.3-alpine3.21 +FROM ruby:3.3-alpine3.21@sha256:e5eafee50c2e178c9fef8973be6180850518d1d60eed116dced53f4478369f42 WORKDIR /app ENV RUBY_YJIT_ENABLE=1 RUN apk add --no-cache build-base diff --git a/frameworks/web-server/ruby/falcon/Dockerfile.glibc b/frameworks/web-server/ruby/falcon/Dockerfile.glibc index b4121e9..ba46c75 100644 --- a/frameworks/web-server/ruby/falcon/Dockerfile.glibc +++ b/frameworks/web-server/ruby/falcon/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM ruby:3.3-bookworm +FROM ruby:3.3-bookworm@sha256:b899e7de6ffa6e974d947202d3ce2ac921c12e46c14cff3106214079388dcff4 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/* COPY Gemfile ./ diff --git a/frameworks/web-server/ruby/falcon/Dockerfile.musl b/frameworks/web-server/ruby/falcon/Dockerfile.musl index fd15b80..7b389d6 100644 --- a/frameworks/web-server/ruby/falcon/Dockerfile.musl +++ b/frameworks/web-server/ruby/falcon/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM ruby:3.3-alpine3.21 +FROM ruby:3.3-alpine3.21@sha256:e5eafee50c2e178c9fef8973be6180850518d1d60eed116dced53f4478369f42 WORKDIR /app RUN apk add --no-cache build-base COPY Gemfile ./ diff --git a/frameworks/web-server/ruby/iodine/Dockerfile.glibc b/frameworks/web-server/ruby/iodine/Dockerfile.glibc index 6135db8..847a023 100644 --- a/frameworks/web-server/ruby/iodine/Dockerfile.glibc +++ b/frameworks/web-server/ruby/iodine/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM ruby:3.3-bookworm +FROM ruby:3.3-bookworm@sha256:b899e7de6ffa6e974d947202d3ce2ac921c12e46c14cff3106214079388dcff4 WORKDIR /app ENV RUBY_YJIT_ENABLE=1 RUN apt-get update -qq && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/* diff --git a/frameworks/web-server/ruby/iodine/Dockerfile.musl b/frameworks/web-server/ruby/iodine/Dockerfile.musl index 171d268..de09eb7 100644 --- a/frameworks/web-server/ruby/iodine/Dockerfile.musl +++ b/frameworks/web-server/ruby/iodine/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM ruby:3.3-alpine3.21 +FROM ruby:3.3-alpine3.21@sha256:e5eafee50c2e178c9fef8973be6180850518d1d60eed116dced53f4478369f42 WORKDIR /app ENV RUBY_YJIT_ENABLE=1 RUN apk add --no-cache build-base diff --git a/frameworks/web-server/rust/actix-web/Dockerfile.glibc b/frameworks/web-server/rust/actix-web/Dockerfile.glibc index 179f144..a44dda9 100644 --- a/frameworks/web-server/rust/actix-web/Dockerfile.glibc +++ b/frameworks/web-server/rust/actix-web/Dockerfile.glibc @@ -1,9 +1,9 @@ -FROM rust:1.88-bookworm AS builder +FROM rust:1.88-bookworm@sha256:af306cfa71d987911a781c37b59d7d67d934f49684058f96cf72079c3626bfe0 AS builder WORKDIR /usr/src/app COPY . . RUN cargo build --release -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/src/app/target/release/actix-benchmark /app/server diff --git a/frameworks/web-server/rust/actix-web/Dockerfile.musl b/frameworks/web-server/rust/actix-web/Dockerfile.musl index adb7c57..37b1364 100644 --- a/frameworks/web-server/rust/actix-web/Dockerfile.musl +++ b/frameworks/web-server/rust/actix-web/Dockerfile.musl @@ -1,10 +1,10 @@ -FROM rust:1.88-alpine3.21 AS builder +FROM rust:1.88-alpine3.21@sha256:54e937b1530d435dc83b94f5a61ef08365127f2fefbb3789712c5d6f55bbb58c AS builder WORKDIR /usr/src/app RUN apk add --no-cache musl-dev COPY . . RUN cargo build --release -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache ca-certificates curl COPY --from=builder /usr/src/app/target/release/actix-benchmark /app/server diff --git a/frameworks/web-server/rust/cpeak/Dockerfile.glibc b/frameworks/web-server/rust/cpeak/Dockerfile.glibc index 375288c..a4b5615 100644 --- a/frameworks/web-server/rust/cpeak/Dockerfile.glibc +++ b/frameworks/web-server/rust/cpeak/Dockerfile.glibc @@ -1,9 +1,9 @@ -FROM rust:1.85-bookworm AS builder +FROM rust:1.85-bookworm@sha256:e51d0265072d2d9d5d320f6a44dde6b9ef13653b035098febd68cce8fa7c0bc4 AS builder WORKDIR /usr/src/app COPY . . RUN cargo build --release -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/src/app/target/release/cpeak-benchmark /app/server diff --git a/frameworks/web-server/rust/cpeak/Dockerfile.musl b/frameworks/web-server/rust/cpeak/Dockerfile.musl index 3b4116f..f9edfc2 100644 --- a/frameworks/web-server/rust/cpeak/Dockerfile.musl +++ b/frameworks/web-server/rust/cpeak/Dockerfile.musl @@ -1,10 +1,10 @@ -FROM rust:1.85-alpine3.21 AS builder +FROM rust:1.85-alpine3.21@sha256:4333721398de61f53ccbe53b0b855bcc4bb49e55828e8f652d7a8ac33dd0c118 AS builder WORKDIR /usr/src/app RUN apk add --no-cache musl-dev COPY . . RUN cargo build --release -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache ca-certificates curl COPY --from=builder /usr/src/app/target/release/cpeak-benchmark /app/server diff --git a/frameworks/web-server/rust/ntex/Dockerfile.glibc b/frameworks/web-server/rust/ntex/Dockerfile.glibc index 5cfa1d8..d737fba 100644 --- a/frameworks/web-server/rust/ntex/Dockerfile.glibc +++ b/frameworks/web-server/rust/ntex/Dockerfile.glibc @@ -1,9 +1,9 @@ -FROM rust:1.88-bookworm AS builder +FROM rust:1.88-bookworm@sha256:af306cfa71d987911a781c37b59d7d67d934f49684058f96cf72079c3626bfe0 AS builder WORKDIR /usr/src/app COPY . . RUN cargo build --release -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/src/app/target/release/ntex-benchmark /app/server diff --git a/frameworks/web-server/rust/ntex/Dockerfile.musl b/frameworks/web-server/rust/ntex/Dockerfile.musl index eeed32a..83bf854 100644 --- a/frameworks/web-server/rust/ntex/Dockerfile.musl +++ b/frameworks/web-server/rust/ntex/Dockerfile.musl @@ -1,10 +1,10 @@ -FROM rust:1.88-alpine3.21 AS builder +FROM rust:1.88-alpine3.21@sha256:54e937b1530d435dc83b94f5a61ef08365127f2fefbb3789712c5d6f55bbb58c AS builder WORKDIR /usr/src/app RUN apk add --no-cache musl-dev COPY . . RUN cargo build --release -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache ca-certificates curl COPY --from=builder /usr/src/app/target/release/ntex-benchmark /app/server diff --git a/frameworks/web-server/zig/dusty/Dockerfile.glibc b/frameworks/web-server/zig/dusty/Dockerfile.glibc index acaa6a1..3f199af 100644 --- a/frameworks/web-server/zig/dusty/Dockerfile.glibc +++ b/frameworks/web-server/zig/dusty/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim AS builder +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 AS builder WORKDIR /usr/src/app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl git xz-utils && rm -rf /var/lib/apt/lists/* RUN curl -fsSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig @@ -6,7 +6,7 @@ RUN git init /opt/dusty && git -C /opt/dusty remote add origin https://github.co COPY . . RUN ln -s /opt/dusty dusty && zig build -Doptimize=ReleaseFast -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/src/app/zig-out/bin/server /app/server diff --git a/frameworks/web-server/zig/dusty/Dockerfile.musl b/frameworks/web-server/zig/dusty/Dockerfile.musl index d9b6fd8..95aa221 100644 --- a/frameworks/web-server/zig/dusty/Dockerfile.musl +++ b/frameworks/web-server/zig/dusty/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM alpine:3.21 AS builder +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 AS builder WORKDIR /usr/src/app RUN apk add --no-cache ca-certificates curl git xz RUN curl -fsSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig @@ -6,7 +6,7 @@ RUN git init /opt/dusty && git -C /opt/dusty remote add origin https://github.co COPY . . RUN ln -s /opt/dusty dusty && zig build -Doptimize=ReleaseFast -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache ca-certificates curl COPY --from=builder /usr/src/app/zig-out/bin/server /app/server diff --git a/frameworks/web-server/zig/swerver/Dockerfile.glibc b/frameworks/web-server/zig/swerver/Dockerfile.glibc index ca6a142..b7b009a 100644 --- a/frameworks/web-server/zig/swerver/Dockerfile.glibc +++ b/frameworks/web-server/zig/swerver/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim AS builder +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 AS builder WORKDIR /usr/src/app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl git xz-utils zlib1g-dev && rm -rf /var/lib/apt/lists/* RUN curl -fsSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig @@ -6,7 +6,7 @@ RUN git init /opt/swerver && git -C /opt/swerver remote add origin https://githu COPY src/main.zig /opt/swerver/src/main.zig RUN cd /opt/swerver && zig build -Doptimize=ReleaseFast -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* COPY --from=builder /opt/swerver/zig-out/bin/swerver /app/server diff --git a/frameworks/web-server/zig/swerver/Dockerfile.musl b/frameworks/web-server/zig/swerver/Dockerfile.musl index 7c66c70..dd2e34a 100644 --- a/frameworks/web-server/zig/swerver/Dockerfile.musl +++ b/frameworks/web-server/zig/swerver/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM alpine:3.21 AS builder +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 AS builder WORKDIR /usr/src/app RUN apk add --no-cache ca-certificates curl git xz zlib-dev RUN curl -fsSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig @@ -6,7 +6,7 @@ RUN git init /opt/swerver && git -C /opt/swerver remote add origin https://githu COPY src/main.zig /opt/swerver/src/main.zig RUN cd /opt/swerver && zig build -Doptimize=ReleaseFast -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache ca-certificates curl COPY --from=builder /opt/swerver/zig-out/bin/swerver /app/server diff --git a/frameworks/web-server/zig/uwebzockets/Dockerfile.glibc b/frameworks/web-server/zig/uwebzockets/Dockerfile.glibc index da3c99e..010af01 100644 --- a/frameworks/web-server/zig/uwebzockets/Dockerfile.glibc +++ b/frameworks/web-server/zig/uwebzockets/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim AS builder +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 AS builder WORKDIR /usr/src/app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl git xz-utils && rm -rf /var/lib/apt/lists/* RUN curl -sSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig @@ -7,7 +7,7 @@ COPY . . RUN ln -s /opt/zuws uz RUN zig build -Doptimize=ReleaseFast -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 WORKDIR /app RUN apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/src/app/zig-out/bin/server /app/server diff --git a/frameworks/web-server/zig/uwebzockets/Dockerfile.musl b/frameworks/web-server/zig/uwebzockets/Dockerfile.musl index becdb81..1632b79 100644 --- a/frameworks/web-server/zig/uwebzockets/Dockerfile.musl +++ b/frameworks/web-server/zig/uwebzockets/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM alpine:3.21 AS builder +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 AS builder WORKDIR /usr/src/app RUN apk add --no-cache ca-certificates curl git xz RUN curl -sSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | tar -xJ -C /opt && ln -s /opt/zig-x86_64-linux-0.16.0/zig /usr/local/bin/zig @@ -7,7 +7,7 @@ COPY . . RUN ln -s /opt/zuws uz RUN zig build -Doptimize=ReleaseFast -FROM alpine:3.21 +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 WORKDIR /app RUN apk add --no-cache ca-certificates curl COPY --from=builder /usr/src/app/zig-out/bin/server /app/server diff --git a/scripts/run_benchmarks_all.sh b/scripts/run_benchmarks_all.sh index c53be40..3a97c38 100755 --- a/scripts/run_benchmarks_all.sh +++ b/scripts/run_benchmarks_all.sh @@ -7,4 +7,7 @@ repo_root=$(CDPATH= cd "$script_dir/.." && pwd) "$script_dir/validate_environment.sh" cd "$repo_root" cargo build --release --bin orchestrator +if [ -n "${BENCHMARK_LOAD_CPUS:-}" ]; then + exec taskset --cpu-list "$BENCHMARK_LOAD_CPUS" ./target/release/orchestrator frameworks results +fi exec ./target/release/orchestrator frameworks results diff --git a/scripts/verify_host.sh b/scripts/verify_host.sh index 5730349..b93f583 100644 --- a/scripts/verify_host.sh +++ b/scripts/verify_host.sh @@ -1,31 +1,90 @@ -#!/bin/sh +#!/usr/bin/env sh set -eu -echo "==> Verifying host benchmarking conditions..." +strict=0 +if [ "$#" -eq 1 ] && [ "$1" = --require-dedicated ]; then + strict=1 +elif [ "$#" -ne 0 ]; then + echo 'usage: verify_host.sh [--require-dedicated]' >&2 + exit 2 +fi + +failed=0 + +report() { + level=$1 + shift + printf '%s: %s\n' "$level" "$*" + [ "$level" != FAIL ] || failed=1 +} -# Check CPU scaling governor -if [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]; then - gov=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor) - if [ "$gov" != "performance" ]; then - echo "WARN: CPU governor is '$gov' (expected 'performance')." +require() { + if [ "$strict" -eq 1 ]; then + report FAIL "$*" else - echo "PASS: CPU governor is configured to 'performance'." + report WARN "$*" fi -fi +} -# Check cgroups v2 mount -if grep -q "cgroup2" /proc/mounts; then - echo "PASS: cgroups v2 unified hierarchy mounted." -else - echo "WARN: cgroups v2 not detected in /proc/mounts." -fi +validate_cpu_set() { + name=$1 + value=$2 + if printf '%s\n' "$value" | grep -Eq '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'; then + taskset -c "$value" true >/dev/null 2>&1 && return + fi + require "$name must be an allowed CPU list" +} -# Check TCP socket backlog -somax=$(cat /proc/sys/net/core/somaxconn) -if [ "$somax" -lt 1024 ]; then - echo "WARN: net.core.somaxconn is $somax (recommend >= 65535 for high concurrency)." -else - echo "PASS: net.core.somaxconn is $somax." -fi +expand_cpu_set() { + printf '%s\n' "$1" | tr ',' '\n' | while IFS= read -r range; do + case $range in + *-*) seq "${range%-*}" "${range#*-}" ;; + *) printf '%s\n' "$range" ;; + esac + done +} + +check_governor() { + governor=/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor + [ -f "$governor" ] || { + require 'CPU governor is unavailable' + return + } + value=$(cat "$governor") + [ "$value" = performance ] && report PASS 'CPU governor is performance' || \ + require "CPU governor is $value, expected performance" +} + +check_cgroup_v2() { + grep -q 'cgroup2' /proc/mounts && report PASS 'cgroups v2 is mounted' || \ + require 'cgroups v2 is not mounted' +} + +check_backlog() { + value=$(cat /proc/sys/net/core/somaxconn) + [ "$value" -ge 65535 ] && report PASS "socket backlog is $value" || \ + require "socket backlog is $value, expected at least 65535" +} + +check_dedicated() { + target=${BENCHMARK_TARGET_CPUS:-} + load=${BENCHMARK_LOAD_CPUS:-} + parent=${BENCHMARK_CGROUP_PARENT:-} + command -v taskset >/dev/null 2>&1 || require 'taskset is required' + [ -n "$target" ] || require 'BENCHMARK_TARGET_CPUS is required' + [ -n "$load" ] || require 'BENCHMARK_LOAD_CPUS is required' + [ -n "$parent" ] || require 'BENCHMARK_CGROUP_PARENT is required' + [ -z "$target" ] || validate_cpu_set BENCHMARK_TARGET_CPUS "$target" + [ -z "$load" ] || validate_cpu_set BENCHMARK_LOAD_CPUS "$load" + if printf '%s\n' "$target,$load" | grep -Eq '^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$'; then + overlap=$( { expand_cpu_set "$target"; expand_cpu_set "$load"; } | sort -n | uniq -d) + [ -z "$overlap" ] || require 'target and load CPU sets must not overlap' + fi +} -echo "Host verification completed." +check_governor +check_cgroup_v2 +check_backlog +[ "$strict" -eq 0 ] || check_dedicated +[ "$failed" -eq 0 ] || exit 1 +report PASS 'host verification completed' diff --git a/templates/docker/Dockerfile.glibc b/templates/docker/Dockerfile.glibc index 3973105..5e70efe 100644 --- a/templates/docker/Dockerfile.glibc +++ b/templates/docker/Dockerfile.glibc @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim AS base +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 AS base WORKDIR /app RUN apt-get update -qq && \ apt-get install -y --no-install-recommends ca-certificates curl && \ diff --git a/templates/docker/Dockerfile.musl b/templates/docker/Dockerfile.musl index 887a7f1..b855e0a 100644 --- a/templates/docker/Dockerfile.musl +++ b/templates/docker/Dockerfile.musl @@ -1,4 +1,4 @@ -FROM alpine:3.21 AS base +FROM alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507 AS base WORKDIR /app RUN apk add --no-cache ca-certificates curl