diff --git a/scripts/run-setc-bench-no-perf.sh b/scripts/run-setc-bench-no-perf.sh new file mode 100755 index 0000000..9ed224a --- /dev/null +++ b/scripts/run-setc-bench-no-perf.sh @@ -0,0 +1,312 @@ +#!/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 — начать всё заново. +PACKAGER='krosh ' +APT_SOURCE=/etc/apt/sources.list.d/alt.list +APT_GET=/usr/lib/apt/apt-get +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 start end status + local libdir="$variant/lib/usr/lib64" + local root="$COMMON/root" + local raw="$variant/raw" + + operation_args "$operation" + apt_options "$root" + + start=$(date +%s%N) + if env LC_ALL=C APT_CONFIG="$COMMON/apt.conf" \ + LD_LIBRARY_PATH="$libdir" \ + taskset -c "$CPU" "$APT_GET" -q \ + "${APT_OPTIONS[@]}" "${ARGS[@]}" \ + >"$raw/$operation.$run.stdout" \ + 2>"$raw/$operation.$run.stderr"; then + status=0 + else + status=$? + fi + end=$(date +%s%N) + + RUN_TIME=$(awk -v start="$start" -v end="$end" \ + 'BEGIN { printf "%.6f", (end - start) / 1000000000 }') + RUN_STATUS=$status +} + +benchmark_variant() +{ + local variant=$1 result=$2 operation run average label status_text + local -a times statuses + local -a operations=( + check + autoremove + install-rpm-build + install-openuds-server + install-password-store + upgrade + dist-upgrade + ) + + mkdir -p "$variant/raw" + printf 'command\taverage_seconds\trun1_seconds\trun2_seconds\trun3_seconds\texit_status\n' \ + >"$result" + + for operation in "${operations[@]}"; do + times=() + statuses=() + + for ((run = 1; run <= RUNS; ++run)); do + run_once "$operation" "$variant" "$run" + times+=("$RUN_TIME") + statuses+=("$RUN_STATUS") + printf '%s: run %d/%d: %ss, status=%s\n' \ + "$operation" "$run" "$RUNS" "$RUN_TIME" "$RUN_STATUS" + done + + average=$(printf '%s\n' "${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\n' \ + "$label" "$average" \ + "${times[0]}" "${times[1]}" "${times[2]}" "$status_text" \ + >>"$result" + done +} + +for command in git gear-hsh hsh rpm rpmquery rpm2cpio cpio apt-get apt-cache \ + taskset awk sed date sha256sum ldd; do + command -v "$command" >/dev/null || fail "required command not found: $command" +done +[[ -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[@]}" + + rm -rf "$variant/lib" + 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" + 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" + + benchmark_variant "$variant" "$RESULT_DIR/$result_name" +done + +printf '\nResults:\n' +printf ' %s\n' "$RESULT_DIR"/*.tsv diff --git a/scripts/run-setc-bench.sh b/scripts/run-setc-bench.sh index 9ed224a..759294c 100755 --- a/scripts/run-setc-bench.sh +++ b/scripts/run-setc-bench.sh @@ -9,6 +9,10 @@ RESULT_DIR="$HOME/res" RUNS=3 CPU=0 RESET_WORK=1 # 0 — продолжить готовые сборки, 1 — начать всё заново. +COLLECT_PERF=1 +PERF_RECORD=1 # Отдельный профильный прогон; не входит в среднее время. +PERF_FREQUENCY=99 +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 ' APT_SOURCE=/etc/apt/sources.list.d/alt.list APT_GET=/usr/lib/apt/apt-get @@ -89,19 +93,34 @@ operation_label() run_once() { - local operation=$1 variant=$2 run=$3 start end status + local operation=$1 variant=$2 run=$3 start end status perf_stat 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 start=$(date +%s%N) if env LC_ALL=C APT_CONFIG="$COMMON/apt.conf" \ LD_LIBRARY_PATH="$libdir" \ - taskset -c "$CPU" "$APT_GET" -q \ - "${APT_OPTIONS[@]}" "${ARGS[@]}" \ + taskset -c "$CPU" "${command[@]}" \ >"$raw/$operation.$run.stdout" \ 2>"$raw/$operation.$run.stderr"; then status=0 @@ -115,9 +134,78 @@ run_once() 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 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 + fi +} + +prepare_perf_symbols() +{ + local perf_dir=$1 debug_file=$2 debuginfo_rpm=$3 + + mkdir -p "$perf_dir/buildid-cache" + cp -a "$debuginfo_rpm" "$perf_dir/" + perf --buildid-dir "$perf_dir/buildid-cache" buildid-cache \ + --add "$debug_file" \ + >"$perf_dir/buildid-cache.stdout" \ + 2>"$perf_dir/buildid-cache.stderr" +} + benchmark_variant() { - local variant=$1 result=$2 operation run average label status_text + local variant=$1 result=$2 debug_file=$3 debuginfo_rpm=$4 + local operation run average label status_text perf_result perf_dir local -a times statuses local -a operations=( check @@ -129,9 +217,20 @@ benchmark_variant() dist-upgrade ) - mkdir -p "$variant/raw" + mkdir -p "$variant/raw/perf-stat" printf 'command\taverage_seconds\trun1_seconds\trun2_seconds\trun3_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" "$debug_file" "$debuginfo_rpm" + fi + fi for operation in "${operations[@]}"; do times=() @@ -141,6 +240,10 @@ benchmark_variant() run_once "$operation" "$variant" "$run" times+=("$RUN_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: %ss, status=%s\n' \ "$operation" "$run" "$RUNS" "$RUN_TIME" "$RUN_STATUS" done @@ -161,13 +264,24 @@ benchmark_variant() "$label" "$average" \ "${times[0]}" "${times[1]}" "${times[2]}" "$status_text" \ >>"$result" + + if ((COLLECT_PERF && PERF_RECORD)); then + printf '%s: perf record\n' "$operation" + record_profile "$operation" "$variant" "$perf_dir" + fi done } for command in git gear-hsh hsh rpm rpmquery rpm2cpio cpio apt-get apt-cache \ - taskset awk sed date sha256sum ldd; do + taskset awk sed date sha256sum ldd readelf; do command -v "$command" >/dev/null || fail "required command not found: $command" done +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' @@ -291,7 +405,16 @@ for setc in "${setc_files[@]}"; do ((${#librpm_rpms[@]} == 1)) || \ fail "expected one librpm7 package for $filename, got ${#librpm_rpms[@]}" - rm -rf "$variant/lib" + 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" @@ -305,7 +428,25 @@ for setc in "${setc_files[@]}"; do grep -F "$libdir/librpm.so.7" >/dev/null || \ fail "apt-get does not load the built librpm for $filename" - benchmark_variant "$variant" "$RESULT_DIR/$result_name" + 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" done printf '\nResults:\n'