487 lines
17 KiB
Bash
Executable File
487 lines
17 KiB
Bash
Executable File
#!/bin/bash
|
||
set -euo pipefail
|
||
export LC_ALL=C
|
||
|
||
# Настройки.
|
||
SETC_DIR="$HOME/setc"
|
||
WORK_ROOT="$HOME/setc-bench"
|
||
RESULT_DIR="$HOME/res"
|
||
RUNS=3
|
||
CPU=0
|
||
RESET_WORK=1 # 0 — продолжить готовые сборки, 1 — начать всё заново.
|
||
COLLECT_PERF=1
|
||
PERF_RECORD=1 # Отдельный профильный прогон; не входит в среднее время.
|
||
PERF_FREQUENCY=499
|
||
PERF_EVENTS='task-clock,context-switches,cpu-migrations,page-faults,minor-faults,major-faults,cycles,instructions,branches,branch-misses,cache-references,cache-misses,L1-dcache-loads,L1-dcache-load-misses,LLC-loads,LLC-load-misses'
|
||
PACKAGER='krosh <gudovdo@my.msu.ru>'
|
||
APT_SOURCE=/etc/apt/sources.list.d/alt.list
|
||
APT_GET=/usr/lib/apt/apt-get
|
||
TIME_COMMAND=/usr/bin/time
|
||
RPM_BUILD_GIT=https://git.altlinux.org/gears/r/rpm-build.git
|
||
RPM_GIT=https://git.altlinux.org/gears/r/rpm.git
|
||
|
||
fail()
|
||
{
|
||
printf 'error: %s\n' "$*" >&2
|
||
exit 1
|
||
}
|
||
|
||
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 set.c benchmark build.\\
|
||
" "$spec"
|
||
}
|
||
|
||
apt_options()
|
||
{
|
||
local root=$1
|
||
|
||
APT_OPTIONS=(
|
||
-o 'Dir::Etc::main=-'
|
||
-o 'Dir::Etc::parts=-'
|
||
-o "Dir::Etc::sourcelist=$COMMON/sources.list"
|
||
-o "Dir::Etc::sourceparts=$COMMON/sources.list.d"
|
||
-o 'Dir::Etc::preferences=-'
|
||
-o 'Dir::Etc::preferencesparts=-'
|
||
-o "Dir::State::lists=$COMMON/lists/"
|
||
-o "Dir::State::status=$COMMON/status"
|
||
-o "Dir::Cache=$COMMON/cache/"
|
||
-o "Dir::Cache::archives=$COMMON/cache/archives"
|
||
-o "Dir::Cache::pkgcache=$COMMON/cache/pkgcache.bin"
|
||
-o "Dir::Cache::srcpkgcache=$COMMON/cache/srcpkgcache.bin"
|
||
-o "RPM::RootDir=$root"
|
||
)
|
||
}
|
||
|
||
operation_args()
|
||
{
|
||
case $1 in
|
||
check) ARGS=(-s check) ;;
|
||
autoremove) ARGS=(-s autoremove) ;;
|
||
install-rpm-build) ARGS=(-s install rpm-build) ;;
|
||
install-openuds-server) ARGS=(-s install openuds-server) ;;
|
||
install-password-store) ARGS=(-s install password-store) ;;
|
||
upgrade) ARGS=(-o APT::Get::EnableUpgrade=true -s upgrade) ;;
|
||
dist-upgrade) ARGS=(-s dist-upgrade) ;;
|
||
*) fail "unknown operation: $1" ;;
|
||
esac
|
||
}
|
||
|
||
operation_label()
|
||
{
|
||
case $1 in
|
||
check) printf '%s' '-s check' ;;
|
||
autoremove) printf '%s' '-s autoremove' ;;
|
||
install-rpm-build) printf '%s' '-s install rpm-build' ;;
|
||
install-openuds-server) printf '%s' '-s install openuds-server' ;;
|
||
install-password-store) printf '%s' '-s install password-store' ;;
|
||
upgrade) printf '%s' '--enable-upgrade -s upgrade' ;;
|
||
dist-upgrade) printf '%s' '-s dist-upgrade' ;;
|
||
esac
|
||
}
|
||
|
||
run_once()
|
||
{
|
||
local operation=$1 variant=$2 run=$3 status perf_stat time_file
|
||
local libdir="$variant/lib/usr/lib64"
|
||
local root="$COMMON/root"
|
||
local raw="$variant/raw"
|
||
local -a command
|
||
|
||
operation_args "$operation"
|
||
apt_options "$root"
|
||
command=("$APT_GET" -q "${APT_OPTIONS[@]}" "${ARGS[@]}")
|
||
|
||
if ((COLLECT_PERF)); then
|
||
perf_stat="$raw/perf-stat/$operation.$run.tsv"
|
||
command=(
|
||
perf stat
|
||
--all-user
|
||
--no-big-num
|
||
-x $'\t'
|
||
-o "$perf_stat"
|
||
-e "$PERF_EVENTS"
|
||
--
|
||
"${command[@]}"
|
||
)
|
||
fi
|
||
|
||
time_file="$raw/$operation.$run.time.tsv"
|
||
if "$TIME_COMMAND" -f $'%U\t%S' -o "$time_file" -- \
|
||
env LC_ALL=C APT_CONFIG="$COMMON/apt.conf" \
|
||
LD_LIBRARY_PATH="$libdir" \
|
||
taskset -c "$CPU" "${command[@]}" \
|
||
>"$raw/$operation.$run.stdout" \
|
||
2>"$raw/$operation.$run.stderr"; then
|
||
status=0
|
||
else
|
||
status=$?
|
||
fi
|
||
read -r RUN_USER_TIME RUN_SYSTEM_TIME <"$time_file"
|
||
RUN_STATUS=$status
|
||
}
|
||
|
||
append_perf_stat()
|
||
{
|
||
local operation=$1 run=$2 raw_file=$3 result_file=$4 label
|
||
|
||
label=$(operation_label "$operation")
|
||
awk -F '\t' -v OFS='\t' -v operation="$label" -v run="$run" '
|
||
/^[[:space:]]*#/ || NF < 3 { next }
|
||
{ print operation, run, $1, $2, $3, $4, $5, $6, $7, $8 }
|
||
' "$raw_file" >>"$result_file"
|
||
}
|
||
|
||
record_profile()
|
||
{
|
||
local operation=$1 variant=$2 perf_dir=$3 dso_name=$4 status data report
|
||
local libdir="$variant/lib/usr/lib64"
|
||
local root="$COMMON/root"
|
||
local -a command
|
||
|
||
operation_args "$operation"
|
||
apt_options "$root"
|
||
data="$perf_dir/$operation.perf.data"
|
||
report="$perf_dir/$operation.report.txt"
|
||
command=("$APT_GET" -q "${APT_OPTIONS[@]}" "${ARGS[@]}")
|
||
|
||
if env LC_ALL=C APT_CONFIG="$COMMON/apt.conf" \
|
||
LD_LIBRARY_PATH="$libdir" \
|
||
taskset -c "$CPU" perf \
|
||
--buildid-dir "$perf_dir/buildid-cache" \
|
||
record \
|
||
--all-user \
|
||
--quiet \
|
||
--freq "$PERF_FREQUENCY" \
|
||
--event cycles \
|
||
--call-graph dwarf,8192 \
|
||
--output "$data" \
|
||
-- "${command[@]}" \
|
||
>"$perf_dir/$operation.stdout" \
|
||
2>"$perf_dir/$operation.stderr"; then
|
||
status=0
|
||
else
|
||
status=$?
|
||
fi
|
||
|
||
printf '%s\n' "$status" >"$perf_dir/$operation.status"
|
||
if [[ -s $data ]]; then
|
||
perf --buildid-dir "$perf_dir/buildid-cache" report \
|
||
--stdio \
|
||
--no-children \
|
||
--percent-limit 0.5 \
|
||
--sort comm,dso,symbol \
|
||
--input "$data" \
|
||
>"$report" \
|
||
2>"$perf_dir/$operation.report.stderr" || true
|
||
|
||
perf --buildid-dir "$perf_dir/buildid-cache" report \
|
||
--stdio \
|
||
--no-children \
|
||
--inline \
|
||
--percent-limit 0 \
|
||
--sort dso,symbol,srcline \
|
||
--input "$data" \
|
||
>"$perf_dir/$operation.lines.txt" \
|
||
2>"$perf_dir/$operation.lines.stderr" || true
|
||
|
||
# Без TUI: perf 6.18 может упасть при выборе символа без self-samples.
|
||
perf --buildid-dir "$perf_dir/buildid-cache" annotate \
|
||
--stdio \
|
||
--dsos "$dso_name" \
|
||
--input "$data" \
|
||
>"$perf_dir/$operation.annotate.txt" \
|
||
2>"$perf_dir/$operation.annotate.stderr" || true
|
||
fi
|
||
}
|
||
|
||
prepare_perf_symbols()
|
||
{
|
||
local perf_dir=$1 runtime_file=$2 debug_file=$3 debuginfo_rpm=$4
|
||
local unstripped="$perf_dir/librpm.unstripped"
|
||
|
||
mkdir -p "$perf_dir/buildid-cache"
|
||
cp -a "$debuginfo_rpm" "$perf_dir/"
|
||
# Split-debug ELF содержит DWARF, но не байты .text для annotate.
|
||
# eu-unstrip объединяет runtime-код и matching debuginfo с тем же Build ID.
|
||
eu-unstrip -o "$unstripped" "$runtime_file" "$debug_file"
|
||
perf --buildid-dir "$perf_dir/buildid-cache" buildid-cache \
|
||
--add "$unstripped" \
|
||
>"$perf_dir/buildid-cache.stdout" \
|
||
2>"$perf_dir/buildid-cache.stderr"
|
||
}
|
||
|
||
benchmark_variant()
|
||
{
|
||
local variant=$1 result=$2 debug_file=$3 debuginfo_rpm=$4
|
||
local runtime_file=$5 dso_name=$6
|
||
local operation run average_user average_system label status_text perf_result perf_dir
|
||
local -a user_times system_times statuses
|
||
local -a operations=(
|
||
check
|
||
autoremove
|
||
install-rpm-build
|
||
install-openuds-server
|
||
install-password-store
|
||
upgrade
|
||
dist-upgrade
|
||
)
|
||
|
||
mkdir -p "$variant/raw/perf-stat"
|
||
printf 'command\taverage_user_seconds\taverage_system_seconds\trun1_user_seconds\trun1_system_seconds\trun2_user_seconds\trun2_system_seconds\trun3_user_seconds\trun3_system_seconds\texit_status\n' \
|
||
>"$result"
|
||
perf_result=${result%.tsv}.perf-stat.tsv
|
||
perf_dir=${result%.tsv}.perf
|
||
if ((COLLECT_PERF)); then
|
||
printf 'command\trun\tvalue\tunit\tevent\tcounter_runtime\trunning_percent\tmetric_value\tmetric_unit\textra\n' \
|
||
>"$perf_result"
|
||
if ((PERF_RECORD)); then
|
||
rm -rf "$perf_dir"
|
||
mkdir -p "$perf_dir"
|
||
prepare_perf_symbols "$perf_dir" "$runtime_file" \
|
||
"$debug_file" "$debuginfo_rpm"
|
||
fi
|
||
fi
|
||
|
||
for operation in "${operations[@]}"; do
|
||
user_times=()
|
||
system_times=()
|
||
statuses=()
|
||
|
||
for ((run = 1; run <= RUNS; ++run)); do
|
||
run_once "$operation" "$variant" "$run"
|
||
user_times+=("$RUN_USER_TIME")
|
||
system_times+=("$RUN_SYSTEM_TIME")
|
||
statuses+=("$RUN_STATUS")
|
||
if ((COLLECT_PERF)); then
|
||
append_perf_stat "$operation" "$run" \
|
||
"$variant/raw/perf-stat/$operation.$run.tsv" "$perf_result"
|
||
fi
|
||
printf '%s: run %d/%d: user=%ss system=%ss status=%s\n' \
|
||
"$operation" "$run" "$RUNS" \
|
||
"$RUN_USER_TIME" "$RUN_SYSTEM_TIME" "$RUN_STATUS"
|
||
done
|
||
|
||
average_user=$(printf '%s\n' "${user_times[@]}" | \
|
||
awk '{ total += $1 } END { printf "%.6f", total / NR }')
|
||
average_system=$(printf '%s\n' "${system_times[@]}" | \
|
||
awk '{ total += $1 } END { printf "%.6f", total / NR }')
|
||
|
||
status_text=${statuses[0]}
|
||
for status in "${statuses[@]}"; do
|
||
if [[ $status != "$status_text" ]]; then
|
||
status_text=$(IFS=,; printf 'mixed:%s' "${statuses[*]}")
|
||
break
|
||
fi
|
||
done
|
||
|
||
label=$(operation_label "$operation")
|
||
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||
"$label" "$average_user" "$average_system" \
|
||
"${user_times[0]}" "${system_times[0]}" \
|
||
"${user_times[1]}" "${system_times[1]}" \
|
||
"${user_times[2]}" "${system_times[2]}" "$status_text" \
|
||
>>"$result"
|
||
|
||
if ((COLLECT_PERF && PERF_RECORD)); then
|
||
printf '%s: perf record\n' "$operation"
|
||
record_profile "$operation" "$variant" "$perf_dir" "$dso_name"
|
||
fi
|
||
done
|
||
}
|
||
|
||
for command in git gear-hsh hsh rpm rpmquery rpm2cpio cpio apt-get apt-cache \
|
||
taskset awk sed date sha256sum ldd readelf readlink eu-unstrip; do
|
||
command -v "$command" >/dev/null || fail "required command not found: $command"
|
||
done
|
||
[[ -x $TIME_COMMAND ]] || fail "time executable not found: $TIME_COMMAND"
|
||
if ((COLLECT_PERF)); then
|
||
command -v perf >/dev/null || \
|
||
fail "perf is not installed; install the ALT package: apt-get install perf"
|
||
perf stat --all-user -e task-clock -- true >/dev/null 2>&1 || \
|
||
fail 'perf events are unavailable; set kernel.perf_event_paranoid=2 or lower'
|
||
fi
|
||
[[ -x $APT_GET ]] || fail "not found: $APT_GET"
|
||
[[ $RUNS -eq 3 ]] || fail 'RUNS must stay equal to 3 for the TSV format below'
|
||
|
||
shopt -s nullglob
|
||
setc_files=("$SETC_DIR"/*.c)
|
||
((${#setc_files[@]} > 0)) || fail "no .c files in $SETC_DIR"
|
||
|
||
if ((RESET_WORK)); then
|
||
# После прерванной сборки часть chroot принадлежит hasher-псевдопользователю;
|
||
# обычный rm её не удалит.
|
||
old_hashers=("$WORK_ROOT"/variants/*/hasher)
|
||
for old_hasher in "${old_hashers[@]}"; do
|
||
[[ -d $old_hasher ]] || continue
|
||
hsh --cleanup-only --workdir="$old_hasher" >/dev/null 2>&1 || true
|
||
done
|
||
rm -rf "$WORK_ROOT"
|
||
fi
|
||
mkdir -p "$WORK_ROOT/src" "$WORK_ROOT/variants" "$RESULT_DIR"
|
||
COMMON="$WORK_ROOT/common"
|
||
if [[ ! -s $COMMON/cache/pkgcache.bin ]]; then
|
||
rm -rf "$COMMON"
|
||
mkdir -p \
|
||
"$COMMON/lists/partial" \
|
||
"$COMMON/cache/archives/partial" \
|
||
"$COMMON/sources.list.d" \
|
||
"$COMMON/root/var/lib/rpm"
|
||
: >"$COMMON/apt.conf"
|
||
: >"$COMMON/status"
|
||
|
||
# Один официальный p11 snapshot без hasher/stuff. Он создаётся до сборок
|
||
# и затем одинаково используется всеми вариантами.
|
||
awk '$1 == "rpm" && /p11\/branch/ && $0 !~ /(hasher|stuff)/ { print }' \
|
||
"$APT_SOURCE" >"$COMMON/sources.list"
|
||
[[ -s $COMMON/sources.list ]] || fail "no p11 sources in $APT_SOURCE"
|
||
|
||
cp -a /var/lib/rpm/. "$COMMON/root/var/lib/rpm/"
|
||
apt_options "$COMMON/root"
|
||
APT_CONFIG="$COMMON/apt.conf" apt-get -qq "${APT_OPTIONS[@]}" update
|
||
APT_CONFIG="$COMMON/apt.conf" apt-cache -q "${APT_OPTIONS[@]}" gencaches >/dev/null
|
||
sha256sum "$COMMON/cache/pkgcache.bin" "$COMMON/cache/srcpkgcache.bin" \
|
||
>"$COMMON/cache.sha256"
|
||
fi
|
||
|
||
# Чистые p11 gear-деревья клонируются один раз.
|
||
if [[ ! -d $WORK_ROOT/src/rpm-build-base/.git ]]; then
|
||
git clone --branch p11 --single-branch "$RPM_BUILD_GIT" \
|
||
"$WORK_ROOT/src/rpm-build-base"
|
||
fi
|
||
if [[ ! -d $WORK_ROOT/src/rpm-base/.git ]]; then
|
||
git clone --branch p11 --single-branch "$RPM_GIT" \
|
||
"$WORK_ROOT/src/rpm-base"
|
||
fi
|
||
|
||
index=0
|
||
for setc in "${setc_files[@]}"; do
|
||
index=$((index + 1))
|
||
id=$(printf 'v%03d' "$index")
|
||
suffix="setc$id"
|
||
filename=$(basename "$setc")
|
||
result_name=${filename%.c}.tsv
|
||
variant="$WORK_ROOT/variants/$id"
|
||
source="$variant/src"
|
||
hasher="$variant/hasher"
|
||
repo="$hasher/repo/x86_64/RPMS.hasher"
|
||
|
||
printf '\n===== %s: %s =====\n' "$id" "$filename"
|
||
mkdir -p "$source" "$hasher" "$variant/logs" "$variant/lib"
|
||
|
||
if [[ ! -d $source/rpm-build/.git ]]; then
|
||
git clone --local "$WORK_ROOT/src/rpm-build-base" "$source/rpm-build"
|
||
git clone --local "$WORK_ROOT/src/rpm-base" "$source/rpm"
|
||
|
||
# По условию один и тот же входной файл кладётся как lib/set.c в оба пакета.
|
||
cp "$setc" "$source/rpm-build/lib/set.c"
|
||
cp "$setc" "$source/rpm/lib/set.c"
|
||
|
||
prepare_spec "$source/rpm-build/rpm-4_0.spec" "$suffix"
|
||
prepare_spec "$source/rpm/alt/rpm.spec" "$suffix"
|
||
else
|
||
cmp -s "$setc" "$source/rpm-build/lib/set.c" && \
|
||
cmp -s "$setc" "$source/rpm/lib/set.c" || \
|
||
fail "$filename changed; set RESET_WORK=1 and restart"
|
||
fi
|
||
|
||
rpm_build_rpms=("$repo"/rpm-build-[0-9]*".$suffix".x86_64.rpm)
|
||
if ((${#rpm_build_rpms[@]} == 0)); then
|
||
(
|
||
cd "$source/rpm-build"
|
||
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-build.log"
|
||
else
|
||
printf 'rpm-build already built: %s\n' "${rpm_build_rpms[0]}"
|
||
fi
|
||
|
||
librpm_rpms=("$repo"/librpm7-[0-9]*".$suffix".x86_64.rpm)
|
||
if ((${#librpm_rpms[@]} == 0)); then
|
||
(
|
||
cd "$source/rpm"
|
||
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 'rpm/librpm7 already built: %s\n' "${librpm_rpms[0]}"
|
||
fi
|
||
|
||
# [0-9]* исключает отдельный пакет librpm7-debuginfo.
|
||
librpm_rpms=("$repo"/librpm7-[0-9]*".$suffix".x86_64.rpm)
|
||
((${#librpm_rpms[@]} == 1)) || \
|
||
fail "expected one librpm7 package for $filename, got ${#librpm_rpms[@]}"
|
||
|
||
debug_file=
|
||
debuginfo_rpm=
|
||
if ((COLLECT_PERF && PERF_RECORD)); then
|
||
debuginfo_rpms=("$repo"/librpm7-debuginfo-[0-9]*".$suffix".x86_64.rpm)
|
||
((${#debuginfo_rpms[@]} == 1)) || \
|
||
fail "expected one librpm7-debuginfo package for $filename, got ${#debuginfo_rpms[@]}"
|
||
debuginfo_rpm=${debuginfo_rpms[0]}
|
||
fi
|
||
|
||
rm -rf "$variant/lib" "$variant/debug"
|
||
mkdir -p "$variant/lib"
|
||
(
|
||
cd "$variant/lib"
|
||
rpm2cpio "${librpm_rpms[0]}" | cpio -idm --quiet
|
||
)
|
||
|
||
libdir="$variant/lib/usr/lib64"
|
||
[[ -e $libdir/librpm.so.7 && -e $libdir/librpmio.so.7 ]] || \
|
||
fail "librpm libraries were not extracted for $filename"
|
||
runtime_file=$(readlink -f "$libdir/librpm.so.7")
|
||
dso_name=$(basename "$runtime_file")
|
||
LD_LIBRARY_PATH="$libdir" ldd "$APT_GET" | \
|
||
grep -F "$libdir/librpm.so.7" >/dev/null || \
|
||
fail "apt-get does not load the built librpm for $filename"
|
||
|
||
if ((COLLECT_PERF && PERF_RECORD)); then
|
||
mkdir -p "$variant/debug"
|
||
(
|
||
cd "$variant/debug"
|
||
rpm2cpio "$debuginfo_rpm" | cpio -idm --quiet
|
||
)
|
||
debug_file="$variant/debug/usr/lib/debug/usr/lib64/librpm.so.7.debug"
|
||
[[ -e $debug_file ]] || fail "librpm debuginfo was not extracted for $filename"
|
||
|
||
main_build_id=$(readelf -n "$libdir/librpm.so.7" | \
|
||
awk '/Build ID:/ { print $3; exit }')
|
||
debug_build_id=$(readelf -n "$debug_file" | \
|
||
awk '/Build ID:/ { print $3; exit }')
|
||
[[ -n $main_build_id && $main_build_id == "$debug_build_id" ]] || \
|
||
fail "librpm/debuginfo Build ID mismatch for $filename"
|
||
fi
|
||
|
||
benchmark_variant "$variant" "$RESULT_DIR/$result_name" \
|
||
"$debug_file" "$debuginfo_rpm" "$runtime_file" "$dso_name"
|
||
done
|
||
|
||
printf '\nResults:\n'
|
||
printf ' %s\n' "$RESULT_DIR"/*.tsv
|