#!/usr/bin/env bash # Reproducible measurements behind COMPARISON.md and the charts. Writes JSON (default docs/charts/bench.json). # Tools that are not installed or not usable are skipped and recorded as null, never guessed. # Usage: scripts/bench.sh [output.json] Environment: BENCH_RUNS (default 10) set -u cd "$(dirname "$0")/.." OUT=${1:-docs/charts/bench.json} RUNS=${BENCH_RUNS:-10} DOOS=$(readlink -f build/doos) [ -x "$DOOS" ] || { echo "bench: build first (make build)" >&2; exit 1; } export DOOS_HOME=$(mktemp -d) trap '"$DOOS" delete --yes ha >/dev/null 2>&1; rm -rf "$DOOS_HOME"' EXIT have() { command -v "$1" >/dev/null 2>&1; } now() { date +%s%N; } # avg_ms LABEL CMD... -> prints average ms per run, or "null" when the command fails avg_ms() { timeout 180 "$@" >/dev/null 2>&1 || { echo null; return; } # warm-up, may pull an image local s e i; s=$(now) for i in $(seq "$RUNS"); do timeout 60 "$@" >/dev/null 2>&1 || { echo null; return; }; done e=$(now); awk -v s="$s" -v e="$e" -v n="$RUNS" 'BEGIN{printf "%.1f", (e-s)/1e6/n}' } note() { echo "bench: $*" >&2; } # ---- setup doos containers (host mode needs nothing, image mode downloads Alpine once) "$DOOS" image add alpine >/dev/null 2>&1 || note "alpine image unavailable, image-mode rows will be null" "$DOOS" get_or_create --net none hn >/dev/null 2>&1 "$DOOS" get_or_create hs >/dev/null 2>&1 "$DOOS" get_or_create --image alpine --net none an >/dev/null 2>&1 "$DOOS" get_or_create --image alpine as >/dev/null 2>&1 "$DOOS" get_or_create --image alpine --net pasta ap >/dev/null 2>&1 BW_BASE="--unshare-user --unshare-pid --unshare-ipc --unshare-uts --die-with-parent --ro-bind /usr /usr --symlink usr/bin /bin --symlink usr/lib /lib --symlink usr/lib64 /lib64 --proc /proc --dev /dev" rows=() add() { rows+=("{\"id\":\"$1\",\"label\":\"$2\",\"group\":\"$3\",\"kind\":\"$4\",\"ms\":$5}"); } echo "bench: startup, $RUNS runs each" >&2 if have bwrap; then add bwrap_none "bubblewrap (host /usr)" none other "$(avg_ms bwrap --unshare-all $BW_BASE true)" add bwrap_net "bubblewrap (host /usr)" default other "$(avg_ms bwrap $BW_BASE true)" fi add doos_host_none "doos host mode" none doos "$(avg_ms "$DOOS" attach hn -- true)" add doos_alpine_none "doos Alpine image" none doos "$(avg_ms "$DOOS" attach an -- true)" add doos_host_slirp "doos host mode" default doos "$(avg_ms "$DOOS" attach hs -- true)" add doos_alpine_slirp "doos Alpine image" default doos "$(avg_ms "$DOOS" attach as -- true)" add doos_alpine_pasta "doos Alpine, pasta" default doos "$(avg_ms "$DOOS" attach ap -- true)" if have podman; then add podman_none "Podman Alpine" none other "$(avg_ms podman run --rm --network none alpine true)" add podman_net "Podman Alpine" default other "$(avg_ms podman run --rm alpine true)" fi if have docker; then add docker_none "Docker Alpine (rootful)" none other "$(avg_ms docker run --rm --network none alpine true)" add docker_net "Docker Alpine (rootful)" default other "$(avg_ms docker run --rm alpine true)" fi # ---- binary sizes size_of() { local p; p=$(command -v "$1" 2>/dev/null) && stat -L -c %s "$p" || echo null; } sizes=() for t in bwrap crun doos docker podman; do if [ "$t" = doos ]; then b=$(stat -c %s "$DOOS"); else b=$(size_of "$t"); fi sizes+=("{\"id\":\"$t\",\"bytes\":$b}") done # ---- cpu limit: 4 busy loops for 4 seconds, CPU seconds consumed (user+sys) cpu_run() { /usr/bin/time -f "%U %S" "$DOOS" attach "$1" -- sh -c 'for i in 1 2 3 4; do (timeout 4 sh -c "while :; do :; done" &); done; sleep 4.2' 2>&1 >/dev/null | tail -1 | awk '{printf "%.2f", $1+$2}' } echo "bench: cpu limit" >&2 "$DOOS" get_or_create hc >/dev/null 2>&1; "$DOOS" get_or_create --cpus 0.5 hc5 >/dev/null 2>&1 cpu_free=$(cpu_run hc); cpu_half=$(cpu_run hc5) "$DOOS" delete --yes hc >/dev/null 2>&1; "$DOOS" delete --yes hc5 >/dev/null 2>&1 ver() { "$@" 2>/dev/null | head -1 | tr -d '"\\' ; } json_join() { local IFS=,; echo "$*"; } cat > "$OUT" <&2