Files
ARSV/scripts/run-sisyphus-set9-d1-bench.sh
2026-08-14 01:52:11 +03:00

937 lines
36 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
set -euo pipefail
export LC_ALL=C
# A/B benchmark:
# set9: librpm из Sisyphus с reimplement/set9.c и исходными pkglist Sisyphus;
# d1: librpm из того же commit с direct_hash/hash_set.c и теми же pkglist,
# заранее преобразованными run_sisyphus_pkglist.py в формат set:D1.
#
# Все измеряемые команды работают без сети. Конвертация, сборка и gencaches
# выполняются до таймера. Порядок замеров в каждом раунде: set9/d1/d1/set9.
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd -- "$SCRIPT_DIR/.." && pwd)
SET9_C=${SET9_C:-$REPO_ROOT/reimplement/set9.c}
D1_C=${D1_C:-$REPO_ROOT/new_version/direct_hash/hash_set.c}
PKGLIST_CONVERTER=${PKGLIST_CONVERTER:-$REPO_ROOT/new_version/direct_hash/apt_benchmark/run_sisyphus_pkglist.py}
SET_REWRITER_C=$REPO_ROOT/new_version/direct_hash/apt_benchmark/rewrite_sisyphus_pkglist.c
SET_COMPAT_H=$REPO_ROOT/scripts/rpmsetcmp/newset_compat.h
ORCHESTRATOR=$(realpath -e -- "${BASH_SOURCE[0]}")
WORK_ROOT=${WORK_ROOT:-$HOME/sisyphus-set9-d1-bench}
RESULT_DIR=${RESULT_DIR:-$WORK_ROOT/results}
SISYPHUS_MIRROR=${SISYPHUS_MIRROR:-https://ftp.altlinux.org/pub/distributions/ALTLinux}
RPM_GIT=${RPM_GIT:-https://git.altlinux.org/gears/r/rpm.git}
RPM_BRANCH=${RPM_BRANCH:-sisyphus}
PACKAGER=${PACKAGER:-krosh <gudovdo@my.msu.ru>}
CPU=${CPU:-0}
ROUNDS=${ROUNDS:-2}
RESET_WORK=${RESET_WORK:-1} # 0 — продолжить подготовку/сборки, 1 — начать заново.
OPERATIONS=${OPERATIONS:-unmet install-rpm-build install-openuds-server install-password-store}
APT_GET=${APT_GET:-/usr/lib/apt/apt-get}
APT_CACHE=${APT_CACHE:-$(command -v apt-cache 2>/dev/null || true)}
usage()
{
cat <<'EOF'
Usage: scripts/run-sisyphus-set9-d1-bench.sh
The script has no positional arguments. Configuration is passed through env:
CPU=2 ROUNDS=3 RESET_WORK=1 ./scripts/run-sisyphus-set9-d1-bench.sh
RESET_WORK=0 OPERATIONS='unmet check' ./scripts/run-sisyphus-set9-d1-bench.sh
Main variables:
WORK_ROOT, RESULT_DIR, SISYPHUS_MIRROR, RPM_GIT, RPM_BRANCH, PACKAGER,
SET9_C, D1_C, PKGLIST_CONVERTER, CPU, ROUNDS, RESET_WORK, OPERATIONS.
RESET_WORK=1 removes WORK_ROOT after running hsh --cleanup-only for old hasher
workdirs. Timed runs never update repositories and never install packages.
Snapshot preparation downloads x86_64 and noarch metadata from SISYPHUS_MIRROR
into an isolated APT directory under WORK_ROOT; host APT configuration is untouched.
EOF
}
fail()
{
printf 'error: %s\n' "$*" >&2
exit 1
}
safe_remove_work_root()
{
local marker="$WORK_ROOT/.arsv-sisyphus-set-bench-root"
[[ -n $WORK_ROOT && $WORK_ROOT == /* && $WORK_ROOT != / &&
$WORK_ROOT != "$HOME_REAL" && $HOME_REAL != "$WORK_ROOT/"* ]] ||
fail "unsafe WORK_ROOT for removal: $WORK_ROOT"
[[ $WORK_ROOT != "$REPO_ROOT" && $REPO_ROOT != "$WORK_ROOT/"* &&
$WORK_ROOT != "$REPO_ROOT/"* && $WORK_ROOT != "$CWD_REAL" &&
$CWD_REAL != "$WORK_ROOT/"* && $WORK_ROOT != "$CWD_REAL/"* ]] ||
fail "WORK_ROOT overlaps the source repository or current directory: $WORK_ROOT"
[[ ! -e $WORK_ROOT || (-f $marker && ! -L $marker) ]] ||
fail "refusing to remove unmarked WORK_ROOT: $WORK_ROOT"
if [[ -f $marker ]]; then
grep -Fx 'ARSV Sisyphus set9/D1 benchmark work root' "$marker" >/dev/null ||
fail "invalid WORK_ROOT ownership marker: $marker"
fi
}
canonical_work_subdir()
{
local path=$1 label=$2 resolved
[[ ! -L $path ]] || fail "$label must not be a symlink: $path"
resolved=$(realpath -e -- "$path") || fail "$label does not resolve: $path"
[[ $resolved == "$WORK_ROOT/"* ]] ||
fail "$label resolves outside WORK_ROOT: $path -> $resolved"
printf '%s' "$resolved"
}
ensure_work_subdir()
{
local path=$1 label=$2 resolved
[[ ! -L $path ]] || fail "$label must not be a symlink: $path"
resolved=$(realpath -m -- "$path")
[[ $resolved == "$WORK_ROOT/"* ]] ||
fail "$label resolves outside WORK_ROOT: $path -> $resolved"
mkdir -p -- "$path"
canonical_work_subdir "$path" "$label" >/dev/null
}
write_snapshot_fingerprint()
{
{
printf 'sisyphus_mirror=%s\n' "$SISYPHUS_MIRROR"
printf 'orchestrator=%s\n' "$(sha256sum "$ORCHESTRATOR" | awk '{print $1}')"
printf 'converter=%s\n' "$(sha256sum "$PKGLIST_CONVERTER" | awk '{print $1}')"
printf 'set9=%s\n' "$(sha256sum "$SET9_C" | awk '{print $1}')"
printf 'd1=%s\n' "$(sha256sum "$D1_C" | awk '{print $1}')"
printf 'rewrite=%s\n' "$(sha256sum "$REPO_ROOT/new_version/direct_hash/apt_benchmark/rewrite_sisyphus_pkglist.c" | awk '{print $1}')"
printf 'compat=%s\n' "$(sha256sum "$REPO_ROOT/scripts/rpmsetcmp/newset_compat.h" | awk '{print $1}')"
[[ ! -f $SNAPSHOT/devel-input-fingerprint.txt ]] ||
cat "$SNAPSHOT/devel-input-fingerprint.txt"
write_runtime_fingerprint
}
}
write_runtime_fingerprint()
{
local rpm_libdir library resolved owner
rpm_libdir=$(rpm --eval '%{_libdir}')
for library in librpm.so.7 librpmio.so.7; do
resolved=$(realpath -e "$rpm_libdir/$library") ||
fail "installed $library not found"
owner=$(rpm -qf --qf '%{NAME}|%{SOURCERPM}|%{DISTTAG}' "$resolved") ||
fail "cannot identify package owning $resolved"
printf 'runtime_%s_owner=%s\n' "$library" "$owner"
printf 'runtime_%s_sha256=%s\n' "$library" \
"$(sha256sum "$resolved" | awk '{print $1}')"
done
}
validate_snapshot_reuse()
{
local current
[[ -f $SNAPSHOT/.complete && -f $SNAPSHOT/input-fingerprint.txt &&
-f $SNAPSHOT/devel-input-fingerprint.txt ]] || return 1
current=$(mktemp)
write_snapshot_fingerprint >"$current"
if ! cmp -s "$current" "$SNAPSHOT/input-fingerprint.txt"; then
rm -f "$current"
fail 'snapshot inputs changed; use RESET_WORK=1'
fi
rm -f "$current"
return 0
}
write_source_fingerprint()
{
{
printf 'rpm_git=%s\n' "$RPM_GIT"
printf 'rpm_branch=%s\n' "$RPM_BRANCH"
printf 'rpm_commit=%s\n' "$(git -C "$SOURCE_BASE" rev-parse HEAD)"
}
}
validate_source_reuse()
{
local current
[[ -f $SOURCE_FINGERPRINT ]] ||
fail 'source fingerprint missing; use RESET_WORK=1'
[[ -z $(git -C "$SOURCE_BASE" status --porcelain) ]] ||
fail 'RPM base source has local changes; use RESET_WORK=1'
current=$(mktemp)
write_source_fingerprint >"$current"
if ! cmp -s "$current" "$SOURCE_FINGERPRINT"; then
rm -f "$current"
fail 'RPM source URL, branch, or commit changed; use RESET_WORK=1'
fi
rm -f "$current"
}
prepare_spec()
{
local spec=$1 suffix=$2 version release new_release date
version=$(sed -n 's/^Version:[[:space:]]*//p' "$spec" | sed -n '1p')
release=$(sed -n 's/^Release:[[:space:]]*//p' "$spec" | sed -n '1p')
[[ -n $version && -n $release ]] || fail "cannot read Version/Release from $spec"
new_release="$release.$suffix"
sed -i "0,/^Release:[[:space:]]*$release$/s//Release: $new_release/" "$spec"
date=$(date '+%a %b %d %Y')
sed -i "/^%changelog/a\\
* $date $PACKAGER $version-$new_release\\
- Local Sisyphus set format benchmark build.\\
" "$spec"
}
prepare_d1_build_corpus()
{
local source=$1 spec helper_dir
spec=$source/alt/rpm.spec
helper_dir=$source/alt/arsv-d1-build
mkdir -p "$helper_dir"
cp "$SET9_C" "$helper_dir/set9.c"
cp "$SET_REWRITER_C" "$helper_dir/rewrite_sisyphus_pkglist.c"
cp "$SET_COMPAT_H" "$helper_dir/newset_compat.h"
python3 - "$spec" <<'PY'
import sys
from pathlib import Path
spec = Path(sys.argv[1])
text = spec.read_text()
needle = "join -o 1.3,2.3 P R |shuf >setcmp-data\n"
replacement = needle + r'''# The buildroot RPM database contains legacy set9 values. This D1 build
# preserves the same relation corpus, but converts both operands before the
# format-specific setcmp/profile checks instead of feeding incompatible input.
mkdir -p arsv-d1-compat
touch arsv-d1-compat/rpmlib.h arsv-d1-compat/system.h arsv-d1-compat/set.h
%__cc %optflags -std=gnu11 -Wall -Wextra -Werror -D_GNU_SOURCE -DARSV_SET9_EXPORT \
-I arsv-d1-compat -include alt/arsv-d1-build/newset_compat.h \
alt/arsv-d1-build/set9.c alt/arsv-d1-build/rewrite_sisyphus_pkglist.c \
-o arsv-convert-set
while read -r set1 set2; do
d1_set1=$(./arsv-convert-set --convert-set "$set1")
d1_set2=$(./arsv-convert-set --convert-set "$set2")
printf '%%s %%s\n' "$d1_set1" "$d1_set2"
done <setcmp-data >setcmp-data.d1
test "$(wc -l <setcmp-data.d1)" -eq "$(wc -l <setcmp-data)"
mv setcmp-data.d1 setcmp-data
'''
if text.count(needle) != 1:
raise SystemExit(f"expected exactly one setcmp corpus creation in {spec}")
spec.write_text(text.replace(needle, replacement))
PY
git -C "$source" add alt/arsv-d1-build
}
apt_options()
{
local variant=$1
APT_OPTIONS=(
-o 'Debug::NoLocking=true'
-o 'Dir::Etc::main=-'
-o 'Dir::Etc::parts=-'
-o "Dir::Etc::sourcelist=$SNAPSHOT/etc-apt/sources.list"
-o "Dir::Etc::sourceparts=$SNAPSHOT/etc-apt/sources.list.d"
-o 'Dir::Etc::preferences=-'
-o 'Dir::Etc::preferencesparts=-'
-o "Dir::State::lists=$variant/apt/lists/"
-o "Dir::State::status=$COMMON/status"
-o "Dir::Cache=$variant/apt/cache/"
-o "Dir::Cache::archives=$variant/apt/cache/archives"
-o "Dir::Cache::pkgcache=$variant/apt/cache/pkgcache.bin"
-o "Dir::Cache::srcpkgcache=$variant/apt/cache/srcpkgcache.bin"
-o "RPM::RootDir=$COMMON/root"
)
}
operation_command()
{
local operation=$1 variant=$2
apt_options "$variant"
case $operation in
unmet)
COMMAND=("$APT_CACHE" -q "${APT_OPTIONS[@]}" unmet)
;;
check)
COMMAND=("$APT_GET" -qq "${APT_OPTIONS[@]}" -s check)
;;
autoremove)
COMMAND=("$APT_GET" -qq "${APT_OPTIONS[@]}" -s autoremove)
;;
install-rpm-build)
COMMAND=("$APT_GET" -qq "${APT_OPTIONS[@]}" -s install rpm-build)
;;
install-openuds-server)
COMMAND=("$APT_GET" -qq "${APT_OPTIONS[@]}" -s install openuds-server)
;;
install-password-store)
COMMAND=("$APT_GET" -qq "${APT_OPTIONS[@]}" -s install password-store)
;;
upgrade)
COMMAND=("$APT_GET" -qq "${APT_OPTIONS[@]}" -o APT::Get::EnableUpgrade=true -s upgrade)
;;
dist-upgrade)
COMMAND=("$APT_GET" -qq "${APT_OPTIONS[@]}" -s dist-upgrade)
;;
*)
fail "unknown operation: $operation"
;;
esac
}
variant_dir()
{
case $1 in
set9) printf '%s' "$SET9_VARIANT" ;;
d1) printf '%s' "$D1_VARIANT" ;;
*) fail "unknown variant: $1" ;;
esac
}
variant_libdir()
{
local variant
variant=$(variant_dir "$1")
printf '%s' "$variant/lib/usr/lib64"
}
prepare_command()
{
local operation=$1 variant_name=$2 variant
variant=$(variant_dir "$variant_name")
RUN_LIBDIR=$(variant_libdir "$variant_name")
operation_command "$operation" "$variant"
}
execute_command()
{
local stdout=$1 stderr=$2 status
if env LC_ALL=C APT_CONFIG="$COMMON/apt.conf" LD_LIBRARY_PATH="$RUN_LIBDIR" \
taskset -c "$CPU" "${COMMAND[@]}" >"$stdout" 2>"$stderr"; then
status=0
else
status=$?
fi
printf '%s' "$status"
}
run_once()
{
local operation=$1 variant_name=$2 sequence=$3 sample=$4
local variant raw stdout stderr start end status elapsed
local stdout_sha stderr_sha stdout_bytes stderr_bytes
variant=$(variant_dir "$variant_name")
raw="$RESULT_DIR/raw/$operation/$variant_name"
mkdir -p "$raw"
stdout="$raw/$sample.stdout"
stderr="$raw/$sample.stderr"
prepare_command "$operation" "$variant_name"
start=$(date +%s%N)
status=$(execute_command "$stdout" "$stderr")
end=$(date +%s%N)
elapsed=$(awk -v start="$start" -v end="$end" \
'BEGIN { printf "%.6f", (end - start) / 1000000000 }')
stdout_sha=$(sha256sum "$stdout" | awk '{print $1}')
stderr_sha=$(sha256sum "$stderr" | awk '{print $1}')
stdout_bytes=$(stat -c %s "$stdout")
stderr_bytes=$(stat -c %s "$stderr")
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$operation" "$sequence" "$variant_name" "$sample" "$elapsed" "$status" \
"$stdout_sha" "$stdout_bytes" "$stderr_sha" "$stderr_bytes" >>"$RAW_RESULTS"
printf '%-28s sequence=%-2s %-4s sample=%-2s %ss status=%s\n' \
"$operation" "$sequence" "$variant_name" "$sample" "$elapsed" "$status"
}
prepare_snapshot()
{
local converter_output devel_cache devel_root rpm_libdir rpm_library rpmio_library
local devel_source runtime_source rpmfile
local -a update_options
local -a rpm_devel_rpms popt_devel_rpms
validate_snapshot_reuse && return
printf '\n===== Preparing one immutable Sisyphus metadata snapshot =====\n'
rm -rf "$SNAPSHOT"
mkdir -p "$SNAPSHOT/lists/partial" \
"$SNAPSHOT/etc-apt/sources.list.d" \
"$SNAPSHOT/download-cache/archives/partial"
printf '%s\n' \
"rpm [alt] $SISYPHUS_MIRROR Sisyphus/x86_64 classic" \
"rpm [alt] $SISYPHUS_MIRROR Sisyphus/noarch classic" \
>"$SNAPSHOT/etc-apt/sources.list"
update_options=(
-o 'Dir::Etc::main=-'
-o 'Dir::Etc::parts=-'
-o "Dir::Etc::sourcelist=$SNAPSHOT/etc-apt/sources.list"
-o "Dir::Etc::sourceparts=$SNAPSHOT/etc-apt/sources.list.d"
-o 'Dir::Etc::preferences=-'
-o 'Dir::Etc::preferencesparts=-'
-o "Dir::State::lists=$SNAPSHOT/lists"
-o "Dir::State::status=$COMMON/status"
-o "Dir::Cache=$SNAPSHOT/download-cache"
-o "Dir::Cache::archives=$SNAPSHOT/download-cache/archives"
-o "Dir::Cache::pkgcache=$SNAPSHOT/download-cache/pkgcache.bin"
-o "Dir::Cache::srcpkgcache=$SNAPSHOT/download-cache/srcpkgcache.bin"
-o "RPM::RootDir=$COMMON/root"
)
APT_CONFIG="$COMMON/apt.conf" "$APT_GET" -qq \
"${update_options[@]}" update
# The converter needs C headers, but the benchmark host intentionally need
# not have development packages installed. Download the p11 packages that
# match the host's installed librpm into a private cache and extract only
# their headers; no system package is installed or changed.
devel_cache="$SNAPSHOT/devel-cache"
devel_root="$SNAPSHOT/devel-root"
mkdir -p "$devel_cache/archives/partial" "$devel_root"
env -u APT_CONFIG "$APT_GET" -qq -y -d \
-o "Dir::Cache=$devel_cache" \
-o "Dir::Cache::archives=$devel_cache/archives" \
-o "Dir::Cache::pkgcache=$devel_cache/pkgcache.bin" \
-o "Dir::Cache::srcpkgcache=$devel_cache/srcpkgcache.bin" \
install librpm-devel
shopt -s nullglob
rpm_devel_rpms=("$devel_cache"/archives/librpm-devel_*.rpm)
popt_devel_rpms=("$devel_cache"/archives/libpopt-devel_*.rpm)
shopt -u nullglob
((${#rpm_devel_rpms[@]} == 1)) ||
fail "expected one downloaded librpm-devel RPM, got ${#rpm_devel_rpms[@]}"
((${#popt_devel_rpms[@]} == 1)) ||
fail "expected one downloaded libpopt-devel RPM, got ${#popt_devel_rpms[@]}"
devel_source=$(rpm -qp --qf '%{SOURCERPM}|%{DISTTAG}' "${rpm_devel_rpms[0]}") ||
fail 'cannot identify downloaded librpm-devel source package'
for rpmfile in "${rpm_devel_rpms[@]}" "${popt_devel_rpms[@]}"; do
(cd "$devel_root" && rpm2cpio "$rpmfile" | cpio -idm --quiet './usr/include/*')
done
[[ -f $devel_root/usr/include/rpm/header.h && -f $devel_root/usr/include/popt.h ]] ||
fail 'failed to extract RPM development headers'
rpm_libdir=$(rpm --eval '%{_libdir}')
rpm_library=$(realpath -e "$rpm_libdir/librpm.so.7") ||
fail 'installed librpm.so.7 not found'
rpmio_library=$(realpath -e "$rpm_libdir/librpmio.so.7") ||
fail 'installed librpmio.so.7 not found'
for rpmfile in "$rpm_library" "$rpmio_library"; do
runtime_source=$(rpm -qf --qf '%{SOURCERPM}|%{DISTTAG}' "$rpmfile") ||
fail "cannot identify runtime package owning $rpmfile"
[[ $runtime_source == "$devel_source" ]] ||
fail "downloaded librpm-devel ($devel_source) does not match $rpmfile ($runtime_source)"
done
{
printf 'devel_librpm_identity=%s|%s\n' \
"$(rpm -qp --qf '%{NAME}|%{EVR}|%{DISTTAG}|%{ARCH}' "${rpm_devel_rpms[0]}")" \
"$devel_source"
printf 'devel_librpm_sha256=%s\n' \
"$(sha256sum "${rpm_devel_rpms[0]}" | awk '{print $1}')"
printf 'devel_popt_identity=%s\n' \
"$(rpm -qp --qf '%{NAME}|%{EVR}|%{DISTTAG}|%{ARCH}' "${popt_devel_rpms[0]}")"
printf 'devel_popt_sha256=%s\n' \
"$(sha256sum "${popt_devel_rpms[0]}" | awk '{print $1}')"
} >"$SNAPSHOT/devel-input-fingerprint.txt"
converter_output="$SNAPSHOT/conversion"
python3 "$PKGLIST_CONVERTER" "$converter_output" \
--lists-dir "$SNAPSHOT/lists" \
--rpm-include-dir "$devel_root/usr/include" \
--rpm-library "$rpm_library" \
--rpm-library "$rpmio_library"
mv "$converter_output/d1-pkglists/manifest.json" "$SNAPSHOT/manifest.json"
mkdir -p "$SNAPSHOT/d1-pkglists"
mv "$converter_output/d1-pkglists/"*.classic "$SNAPSHOT/d1-pkglists/"
rmdir "$converter_output/d1-pkglists" "$converter_output"
rm -rf "$SNAPSHOT/download-cache"
rm -rf "$devel_cache" "$devel_root"
rm -f "$SNAPSHOT/lists/lock"
rm -rf "$SNAPSHOT/lists/partial"
mkdir -p "$SNAPSHOT/lists/partial"
write_snapshot_fingerprint >"$SNAPSHOT/input-fingerprint.txt"
: >"$SNAPSHOT/.complete"
}
prepare_variant_lists()
{
local variant=$1 format=$2 variant_apt
if [[ -d $variant/apt ]]; then
variant_apt=$(canonical_work_subdir "$variant/apt" 'APT variant directory')
chmod -R u+w "$variant_apt"
fi
rm -rf "$variant/apt"
mkdir -p "$variant/apt/lists" "$variant/apt/cache/archives/partial"
cp -a "$SNAPSHOT/lists/." "$variant/apt/lists/"
if [[ $format == d1 ]]; then
python3 - "$SNAPSHOT/manifest.json" "$SNAPSHOT/lists" \
"$SNAPSHOT/d1-pkglists" "$variant/apt/lists" <<'PY'
import hashlib
import json
import shutil
import sys
from pathlib import Path
manifest_path, original_dir, d1_dir, target_dir = map(Path, sys.argv[1:])
manifest = json.loads(manifest_path.read_text())
for architecture, data in manifest["architectures"].items():
source = original_dir / data["source"]["path"]
converted = d1_dir / data["output"]["path"]
target = target_dir / data["source"]["path"]
if hashlib.sha256(source.read_bytes()).hexdigest() != data["source"]["sha256"]:
raise SystemExit(f"source checksum mismatch for {architecture}: {source}")
if hashlib.sha256(converted.read_bytes()).hexdigest() != data["output"]["sha256"]:
raise SystemExit(f"D1 checksum mismatch for {architecture}: {converted}")
shutil.copyfile(converted, target)
PY
fi
}
build_variant()
{
local name=$1 source_c=$2 suffix=$3 variant source hasher repo spec
local expected_fingerprint artifact_fingerprint
local -a librpm_rpms
variant=$(variant_dir "$name")
source="$variant/src/rpm"
hasher="$variant/hasher"
repo="$hasher/repo/x86_64/RPMS.hasher"
spec="$source/alt/rpm.spec"
printf '\n===== Building %s with %s =====\n' "$name" "$source_c"
mkdir -p "$variant/src" "$variant/logs" "$hasher"
shopt -s nullglob
librpm_rpms=("$repo"/librpm7-[0-9]*".$suffix".x86_64.rpm)
expected_fingerprint=$(mktemp)
{
printf 'base_commit=%s\n' "$(git -C "$SOURCE_BASE" rev-parse HEAD)"
printf 'orchestrator=%s\n' "$(sha256sum "$ORCHESTRATOR" | awk '{print $1}')"
printf 'set_source=%s\n' "$(sha256sum "$source_c" | awk '{print $1}')"
printf 'suffix=%s\n' "$suffix"
printf 'packager=%s\n' "$PACKAGER"
if [[ $name == d1 ]]; then
printf 'set9_decoder=%s\n' "$(sha256sum "$SET9_C" | awk '{print $1}')"
printf 'set_rewriter=%s\n' "$(sha256sum "$SET_REWRITER_C" | awk '{print $1}')"
printf 'set_compat=%s\n' "$(sha256sum "$SET_COMPAT_H" | awk '{print $1}')"
fi
} >"$expected_fingerprint"
if [[ -f $variant/input-fingerprint.txt ]]; then
if ! cmp -s "$expected_fingerprint" "$variant/input-fingerprint.txt"; then
rm -f "$expected_fingerprint"
fail "$name build inputs changed; use RESET_WORK=1"
fi
elif ((${#librpm_rpms[@]} > 0)); then
rm -f "$expected_fingerprint"
fail "$name RPM exists without its input fingerprint; use RESET_WORK=1"
fi
if ((${#librpm_rpms[@]} == 0)) && [[ -d $source ]]; then
rm -rf "$source"
fi
if [[ ! -d $source/.git ]]; then
git clone --local "$SOURCE_BASE" "$source"
cp "$source_c" "$source/lib/set.c"
prepare_spec "$spec" "$suffix"
if [[ $name == d1 ]]; then
prepare_d1_build_corpus "$source"
fi
sha256sum "$spec" >"$variant/prepared-spec.sha256"
cp "$expected_fingerprint" "$variant/input-fingerprint.txt"
else
[[ -f $variant/prepared-spec.sha256 ]] &&
sha256sum -c "$variant/prepared-spec.sha256" >/dev/null ||
fail "$name prepared RPM spec changed; use RESET_WORK=1"
cmp -s "$source_c" "$source/lib/set.c" ||
fail "$source_c changed; use RESET_WORK=1"
if [[ $name == d1 ]]; then
cmp -s "$SET9_C" "$source/alt/arsv-d1-build/set9.c" &&
cmp -s "$SET_REWRITER_C" \
"$source/alt/arsv-d1-build/rewrite_sisyphus_pkglist.c" &&
cmp -s "$SET_COMPAT_H" "$source/alt/arsv-d1-build/newset_compat.h" ||
fail 'D1 build corpus converter changed; use RESET_WORK=1'
fi
fi
rm -f "$expected_fingerprint"
librpm_rpms=("$repo"/librpm7-[0-9]*".$suffix".x86_64.rpm)
if ((${#librpm_rpms[@]} == 0)); then
(
cd "$source"
gear-hsh \
--commit \
--with-stuff \
--packager="$PACKAGER" \
--no-sisyphus-check=changelog,packager,gpg \
--mountpoints=/proc \
--workdir="$hasher" \
--target=x86_64
) 2>&1 | tee "$variant/logs/rpm.log"
else
printf 'already built: %s\n' "${librpm_rpms[0]}"
fi
librpm_rpms=("$repo"/librpm7-[0-9]*".$suffix".x86_64.rpm)
((${#librpm_rpms[@]} == 1)) ||
fail "expected one librpm7 package for $name, got ${#librpm_rpms[@]}"
artifact_fingerprint=$(sha256sum "${librpm_rpms[0]}")
if [[ -f $variant/librpm-artifact.sha256 ]]; then
[[ $artifact_fingerprint == "$(<"$variant/librpm-artifact.sha256")" ]] ||
fail "$name librpm artifact changed; use RESET_WORK=1"
else
printf '%s\n' "$artifact_fingerprint" >"$variant/librpm-artifact.sha256"
fi
rm -rf "$variant/lib"
mkdir -p "$variant/lib"
(
cd "$variant/lib"
rpm2cpio "${librpm_rpms[0]}" | cpio -idm --quiet
)
[[ -e $variant/lib/usr/lib64/librpm.so.7 && -e $variant/lib/usr/lib64/librpmio.so.7 ]] ||
fail "librpm libraries were not extracted for $name"
for apt_binary in "$APT_GET" "$APT_CACHE"; do
LD_LIBRARY_PATH="$variant/lib/usr/lib64" ldd "$apt_binary" |
awk -v expected="$variant/lib/usr/lib64/librpm.so.7" '
index($0, expected) { found = 1 }
END { exit found ? 0 : 1 }
' || fail "$apt_binary does not load the built librpm for $name"
done
}
build_cache()
{
local name=$1 variant libdir start end elapsed stdout stderr status
variant=$(variant_dir "$name")
libdir=$(variant_libdir "$name")
apt_options "$variant"
stdout="$variant/logs/gencaches.stdout"
stderr="$variant/logs/gencaches.stderr"
chmod -R u+w "$variant/apt/cache"
rm -f "$variant/apt/cache/pkgcache.bin" "$variant/apt/cache/srcpkgcache.bin"
start=$(date +%s%N)
if env LC_ALL=C APT_CONFIG="$COMMON/apt.conf" LD_LIBRARY_PATH="$libdir" \
"$APT_CACHE" -q "${APT_OPTIONS[@]}" gencaches >"$stdout" 2>"$stderr"; then
status=0
else
status=$?
fi
end=$(date +%s%N)
elapsed=$(awk -v start="$start" -v end="$end" \
'BEGIN { printf "%.6f", (end - start) / 1000000000 }')
printf '%s\t%s\t%s\n' "$name" "$elapsed" "$status" >>"$CACHE_RESULTS"
[[ $status -eq 0 && -s $variant/apt/cache/pkgcache.bin ]] ||
fail "gencaches failed for $name; see $stderr"
chmod -R a-w "$variant/apt/cache"
}
record_cache_checksums()
{
local name variant
: >"$RESULT_DIR/cache-files.before.txt"
for name in set9 d1; do
variant=$(variant_dir "$name")
for cache_file in "$variant/apt/cache/pkgcache.bin" \
"$variant/apt/cache/srcpkgcache.bin"; do
stat -c '%n\t%D\t%i\t%s\t%Y\t%A' "$cache_file"
sha256sum "$cache_file"
done >>"$RESULT_DIR/cache-files.before.txt"
done
}
verify_cache_checksums()
{
local name variant
: >"$RESULT_DIR/cache-files.after.txt"
for name in set9 d1; do
variant=$(variant_dir "$name")
for cache_file in "$variant/apt/cache/pkgcache.bin" \
"$variant/apt/cache/srcpkgcache.bin"; do
stat -c '%n\t%D\t%i\t%s\t%Y\t%A' "$cache_file"
sha256sum "$cache_file"
done >>"$RESULT_DIR/cache-files.after.txt"
done
cmp -s "$RESULT_DIR/cache-files.before.txt" \
"$RESULT_DIR/cache-files.after.txt" ||
fail "APT cache changed during timed runs"
}
write_provenance()
{
{
printf 'rpm_git=%s\n' "$RPM_GIT"
printf 'rpm_branch=%s\n' "$RPM_BRANCH"
printf 'rpm_commit=%s\n' "$(git -C "$SOURCE_BASE" rev-parse HEAD)"
printf 'sisyphus_mirror=%s\n' "$SISYPHUS_MIRROR"
printf 'apt=%s\n' "$(rpmquery --qf '%{VERSION}-%{RELEASE}' apt)"
printf 'rpm=%s\n' "$(rpm --version)"
printf 'cpu=%s\n' "$CPU"
printf 'rounds=%s\n' "$ROUNDS"
printf 'operations=%s\n' "$OPERATIONS"
sha256sum "$ORCHESTRATOR" "$SET9_C" "$D1_C" "$PKGLIST_CONVERTER" \
"$REPO_ROOT/new_version/direct_hash/apt_benchmark/rewrite_sisyphus_pkglist.c" \
"$REPO_ROOT/scripts/rpmsetcmp/newset_compat.h"
cat "$SNAPSHOT/devel-input-fingerprint.txt"
write_runtime_fingerprint
printf 'set9_librpm='; cat "$SET9_VARIANT/librpm-artifact.sha256"
printf 'd1_librpm='; cat "$D1_VARIANT/librpm-artifact.sha256"
} >"$RESULT_DIR/provenance.txt"
cp "$SNAPSHOT/manifest.json" "$RESULT_DIR/pkglist-manifest.json"
}
summarize_results()
{
python3 - "$RAW_RESULTS" "$RESULT_DIR/summary.tsv" "$RESULT_DIR/summary.md" <<'PY'
import csv
import statistics
import sys
from collections import defaultdict
from pathlib import Path
raw_path, tsv_path, markdown_path = map(Path, sys.argv[1:])
with raw_path.open(newline="") as stream:
rows = list(csv.DictReader(stream, delimiter="\t"))
groups = defaultdict(list)
for row in rows:
groups[row["operation"]].append(row)
summary = []
all_equivalent = True
for operation, items in groups.items():
by_variant = defaultdict(list)
signatures = set()
statuses = set()
for item in items:
by_variant[item["variant"]].append(float(item["seconds"]))
statuses.add(item["status"])
signatures.add(
(
item["status"],
item["stdout_sha256"],
item["stdout_bytes"],
item["stderr_sha256"],
item["stderr_bytes"],
)
)
if set(by_variant) != {"set9", "d1"}:
raise SystemExit(f"missing variant samples for {operation}")
if len(by_variant["set9"]) != len(by_variant["d1"]):
raise SystemExit(f"unbalanced variant samples for {operation}")
set9 = statistics.median(by_variant["set9"])
d1 = statistics.median(by_variant["d1"])
equivalent = len(signatures) == 1
all_equivalent &= equivalent
status = next(iter(statuses)) if len(statuses) == 1 else "mixed:" + ",".join(sorted(statuses))
path = "normal" if status == "0" else "failure-path"
summary.append(
(
operation,
len(by_variant["set9"]),
set9,
min(by_variant["set9"]),
max(by_variant["set9"]),
d1,
min(by_variant["d1"]),
max(by_variant["d1"]),
d1 / set9,
status,
path,
equivalent,
)
)
with tsv_path.open("w", newline="") as stream:
writer = csv.writer(stream, delimiter="\t", lineterminator="\n")
writer.writerow(
[
"operation", "runs_per_variant", "set9_median_seconds", "set9_min_seconds",
"set9_max_seconds", "d1_median_seconds", "d1_min_seconds", "d1_max_seconds",
"d1/set9", "exit_status", "benchmark_path", "outputs_equal"
]
)
for operation, count, set9, set9_min, set9_max, d1, d1_min, d1_max, ratio, status, path, equivalent in summary:
writer.writerow(
[
operation, count, f"{set9:.6f}", f"{set9_min:.6f}", f"{set9_max:.6f}",
f"{d1:.6f}", f"{d1_min:.6f}", f"{d1_max:.6f}", f"{ratio:.4f}",
status, path, "yes" if equivalent else "NO"
]
)
lines = [
"# Sisyphus set9 vs D1 benchmark",
"",
"Timed scope: resolver commands only; conversion, builds and `gencaches` are excluded.",
"Each round uses the balanced order `set9 / d1 / d1 / set9` after one warm-up per variant.",
"",
"| operation | runs/variant | set9 median [minmax], s | D1 median [minmax], s | D1/set9 | status/path | output equivalence |",
"|---|---:|---:|---:|---:|:---:|:---:|",
]
for operation, count, set9, set9_min, set9_max, d1, d1_min, d1_max, ratio, status, path, equivalent in summary:
lines.append(
f"| `{operation}` | {count} | {set9:.6f} [{set9_min:.6f}{set9_max:.6f}] | "
f"{d1:.6f} [{d1_min:.6f}{d1_max:.6f}] | {ratio:.4f} | "
f"{status}/{path} | {'yes' if equivalent else '**NO**'} |"
)
lines.extend(
[
"",
"`D1/set9 < 1` means that the D1 variant was faster.",
"Rows with non-zero status are explicitly labelled `failure-path`; do not treat them as successful resolver workloads.",
"Output equivalence includes exit status plus exact stdout and stderr hashes for every repeat and both variants.",
]
)
markdown_path.write_text("\n".join(lines) + "\n")
if not all_equivalent:
raise SystemExit(2)
PY
}
if [[ ${1:-} == --help || ${1:-} == -h ]]; then
usage
exit 0
fi
(($# == 0)) || fail "unexpected arguments; use --help"
for command in git gear-hsh hsh rpm rpmquery rpm2cpio cpio taskset awk sed cc \
pkglist-query date sha256sum stat ldd python3 cmp cp mv tee realpath mktemp grep; do
command -v "$command" >/dev/null || fail "required command not found: $command"
done
[[ -n $APT_CACHE && -x $APT_CACHE ]] || fail "apt-cache not found: $APT_CACHE"
[[ -x $APT_GET ]] || fail "apt-get executable not found: $APT_GET"
[[ -f $SET9_C ]] || fail "set9 source not found: $SET9_C"
[[ -f $D1_C ]] || fail "D1 source not found: $D1_C"
[[ -f $PKGLIST_CONVERTER ]] || fail "pkglist converter not found: $PKGLIST_CONVERTER"
[[ $ROUNDS =~ ^[1-9][0-9]*$ ]] || fail "ROUNDS must be a positive integer"
[[ $RESET_WORK =~ ^[01]$ ]] || fail "RESET_WORK must be 0 or 1"
taskset -c "$CPU" true >/dev/null 2>&1 || fail "CPU $CPU is unavailable to taskset"
read -r -a OPERATION_LIST <<<"$OPERATIONS"
((${#OPERATION_LIST[@]} > 0)) || fail "OPERATIONS is empty"
for operation in "${OPERATION_LIST[@]}"; do
case $operation in
unmet|check|autoremove|install-rpm-build|install-openuds-server|install-password-store|upgrade|dist-upgrade) ;;
*) fail "unknown operation in OPERATIONS: $operation" ;;
esac
done
HOME_REAL=$(realpath -m "$HOME")
CWD_REAL=$(realpath -m "$PWD")
REPO_ROOT=$(realpath -m "$REPO_ROOT")
SET9_C=$(realpath -m "$SET9_C")
D1_C=$(realpath -m "$D1_C")
PKGLIST_CONVERTER=$(realpath -m "$PKGLIST_CONVERTER")
WORK_ROOT=$(realpath -m "$WORK_ROOT")
RESULT_DIR=$(realpath -m "$RESULT_DIR")
[[ $RESULT_DIR == "$WORK_ROOT"/* ]] ||
fail "RESULT_DIR must be inside WORK_ROOT: $RESULT_DIR"
COMMON="$WORK_ROOT/common"
SNAPSHOT="$COMMON/snapshot"
SOURCE_BASE="$WORK_ROOT/src/rpm-base"
SOURCE_FINGERPRINT="$WORK_ROOT/src/rpm-base.fingerprint.txt"
SET9_VARIANT="$WORK_ROOT/variants/set9"
D1_VARIANT="$WORK_ROOT/variants/d1"
WORK_ROOT_EXISTED=0
[[ -e $WORK_ROOT ]] && WORK_ROOT_EXISTED=1
if ((RESET_WORK)); then
safe_remove_work_root
for old_hasher in "$SET9_VARIANT/hasher" "$D1_VARIANT/hasher"; do
[[ -d $old_hasher ]] || continue
old_hasher=$(canonical_work_subdir "$old_hasher" 'hasher workdir')
hsh --cleanup-only --workdir="$old_hasher" ||
fail "hasher cleanup failed: $old_hasher"
done
for old_apt in "$SET9_VARIANT/apt" "$D1_VARIANT/apt"; do
[[ -d $old_apt ]] || continue
old_apt=$(canonical_work_subdir "$old_apt" 'APT workdir')
chmod -R u+w "$old_apt"
done
rm -rf "$WORK_ROOT"
WORK_ROOT_EXISTED=0
elif ((WORK_ROOT_EXISTED)); then
marker="$WORK_ROOT/.arsv-sisyphus-set-bench-root"
[[ -f $marker && ! -L $marker ]] ||
fail "refusing unmarked existing WORK_ROOT: $WORK_ROOT"
grep -Fx 'ARSV Sisyphus set9/D1 benchmark work root' "$marker" >/dev/null ||
fail "invalid WORK_ROOT ownership marker: $marker"
fi
mkdir -p "$WORK_ROOT"
MARKER="$WORK_ROOT/.arsv-sisyphus-set-bench-root"
if [[ -e $MARKER ]]; then
grep -Fx 'ARSV Sisyphus set9/D1 benchmark work root' "$MARKER" >/dev/null ||
fail "invalid WORK_ROOT ownership marker: $MARKER"
else
printf '%s\n' 'ARSV Sisyphus set9/D1 benchmark work root' >"$MARKER"
fi
ensure_work_subdir "$WORK_ROOT/src" 'source directory'
ensure_work_subdir "$WORK_ROOT/variants" 'variants directory'
ensure_work_subdir "$COMMON/root/var/lib/rpm" 'isolated RPM database directory'
ensure_work_subdir "$RESULT_DIR" 'results directory'
: >"$COMMON/apt.conf"
: >"$COMMON/status"
prepare_snapshot
if [[ ! -d $SOURCE_BASE/.git ]]; then
git clone --branch "$RPM_BRANCH" --single-branch "$RPM_GIT" "$SOURCE_BASE"
write_source_fingerprint >"$SOURCE_FINGERPRINT"
else
validate_source_reuse
fi
build_variant set9 "$SET9_C" arsvset9
build_variant d1 "$D1_C" arsvd1
prepare_variant_lists "$SET9_VARIANT" set9
prepare_variant_lists "$D1_VARIANT" d1
mkdir -p "$RESULT_DIR/raw"
CACHE_RESULTS="$RESULT_DIR/cache-build.tsv"
printf 'variant\tseconds\texit_status\n' >"$CACHE_RESULTS"
build_cache set9
build_cache d1
record_cache_checksums
write_provenance
RAW_RESULTS="$RESULT_DIR/raw.tsv"
printf 'operation\tsequence\tvariant\tsample\tseconds\tstatus\tstdout_sha256\tstdout_bytes\tstderr_sha256\tstderr_bytes\n' >"$RAW_RESULTS"
declare -A SAMPLE_COUNTS=([set9]=0 [d1]=0)
sequence=0
for operation in "${OPERATION_LIST[@]}"; do
printf '\n===== Warm-up: %s =====\n' "$operation"
mkdir -p "$RESULT_DIR/warmup/$operation"
for variant_name in set9 d1; do
prepare_command "$operation" "$variant_name"
warm_status=$(execute_command \
"$RESULT_DIR/warmup/$operation/$variant_name.stdout" \
"$RESULT_DIR/warmup/$operation/$variant_name.stderr")
printf '%s\n' "$warm_status" >"$RESULT_DIR/warmup/$operation/$variant_name.status"
done
printf '===== Timed ABBA: %s =====\n' "$operation"
for ((round = 1; round <= ROUNDS; ++round)); do
for variant_name in set9 d1 d1 set9; do
sequence=$((sequence + 1))
SAMPLE_COUNTS[$variant_name]=$((SAMPLE_COUNTS[$variant_name] + 1))
run_once "$operation" "$variant_name" "$sequence" "${SAMPLE_COUNTS[$variant_name]}"
done
done
SAMPLE_COUNTS[set9]=0
SAMPLE_COUNTS[d1]=0
done
verify_cache_checksums
if ! summarize_results; then
fail "variant/repeat outputs differ; inspect $RESULT_DIR/raw and $RESULT_DIR/summary.md"
fi
printf '\nDone. Results:\n'
printf ' %s\n' "$RESULT_DIR/summary.md" "$RESULT_DIR/summary.tsv" \
"$RESULT_DIR/raw.tsv" "$RESULT_DIR/cache-build.tsv" "$RESULT_DIR/provenance.txt" \
"$RESULT_DIR/pkglist-manifest.json"