doctor: report exactly what is missing and how to install it

Lists each dnf-provisioned tool and app with its package, flags bundled
dependencies (PassMark, llama-bench, model, matplotlib) and prints a RESULT
verdict with a ready-to-run dnf install command. App packages are now included
in the missing/package accounting.
This commit is contained in:
smill 2026-09-29 21:58:55 -04:00
commit f9c1e2d40e

View file

@ -139,6 +139,8 @@ PKG_FOR_TOOL_CMD="7z:7zip openssl:openssl fio:fio stress-ng:stress-ng \
sensors:lm_sensors turbostat:kernel-tools cpupower:cpupowerutils \ sensors:lm_sensors turbostat:kernel-tools cpupower:cpupowerutils \
glmark2:glmark2 vkmark:vkmark glxinfo:mesa-demos vulkaninfo:vulkan-tools \ glmark2:glmark2 vkmark:vkmark glxinfo:mesa-demos vulkaninfo:vulkan-tools \
sysbench:sysbench" sysbench:sysbench"
# App workloads map to their own packages; included in the missing/pkg checks.
PKG_FOR_APP_CMD="libreoffice:libreoffice-writer inkscape:inkscape gegl:gegl04-tools"
missing_tools() { # prints tool names whose command is absent missing_tools() { # prints tool names whose command is absent
local pair cmd local pair cmd
@ -147,9 +149,16 @@ missing_tools() { # prints tool names whose command is absent
have "$cmd" || printf '%s\n' "$cmd" have "$cmd" || printf '%s\n' "$cmd"
done done
} }
missing_apps() {
local pair cmd
for pair in $PKG_FOR_APP_CMD; do
cmd="${pair%%:*}"
have "$cmd" || printf '%s\n' "$cmd"
done
}
pkgs_for_missing() { # prints the packages needed for the missing commands pkgs_for_missing() { # prints the packages needed for the missing commands
local pkgs="" pair cmd local pkgs="" pair cmd
for pair in $PKG_FOR_TOOL_CMD; do for pair in $PKG_FOR_TOOL_CMD $PKG_FOR_APP_CMD; do
cmd="${pair%%:*}"; pkg="${pair#*:}" cmd="${pair%%:*}"; pkg="${pair#*:}"
have "$cmd" || pkgs="$pkgs $pkg" have "$cmd" || pkgs="$pkgs $pkg"
done done
@ -374,40 +383,68 @@ pin_gpu_env() {
# --- readiness check (no benchmarks run) ------------------------------------- # --- readiness check (no benchmarks run) -------------------------------------
doctor() { doctor() {
local problems=() warnings=()
section "basic-benchmark doctor ($(hostname))" section "basic-benchmark doctor ($(hostname))"
printf 'kit: %s\n' "$DIR" printf 'kit: %s\n' "$DIR"
printf 'user: %s\n' "$(id -un)" printf 'user: %s\n' "$(id -un)"
printf 'TERM: %s\n' "${TERM:-<unset>}" printf 'TERM: %s\n' "${TERM:-<unset>}"
printf 'display: %s\n' "${WAYLAND_DISPLAY:-}${DISPLAY:-<none (headed part would refuse)>}" printf 'display: %s\n' "${WAYLAND_DISPLAY:-}${DISPLAY:-<none>}"
if sudo -n true 2>/dev/null; then printf 'sudo: cached (ok)\n' if sudo -n true 2>/dev/null; then printf 'sudo: cached (ok)\n'
else printf 'sudo: will prompt - run the headless part as: ssh -t <target> ...\n'; fi else
printf 'sudo: will prompt for a password\n'
warnings+=("run the headless part as 'ssh -t <target> ...' so sudo can prompt")
fi
detect_dgpu detect_dgpu
printf 'dGPU: pci=%s vd=%s\n' "${GPU_PCI:-<none>}" "${GPU_VD:-<none>}" printf 'dGPU: pci=%s vd=%s\n' "${GPU_PCI:-<none>}" "${GPU_VD:-<none>}"
printf 'RAPL: %s\n' "$(ls -d /sys/class/powercap/intel-rapl:* 2>/dev/null | tr '\n' ' ')" printf 'RAPL: %s\n' "$(ls -d /sys/class/powercap/intel-rapl:* 2>/dev/null | tr '\n' ' ')"
printf 'PassMark: %s\n' "$([ -x "$PT" ] && echo "$PT" || echo "MISSING ($PT)")"
printf 'llama: %s\n' "$([ -x "$LLAMA_BIN" ] && echo "$LLAMA_BIN" || echo "MISSING ($LLAMA_BIN)")"
printf 'model: %s\n' "$([ -f "$LLAMA_MODEL" ] && echo "$LLAMA_MODEL ($(du -h "$LLAMA_MODEL" 2>/dev/null | cut -f1))" || echo "MISSING ($LLAMA_MODEL)")"
printf 'tools:\n'
for t in 7z openssl sysbench fio stress-ng sensors turbostat cpupower \
glmark2 vkmark glxinfo vulkaninfo nproc; do
printf ' %-22s %s\n' "$t" "$(command -v "$t" || echo MISSING)"
done
printf 'apps:\n'
for t in libreoffice inkscape gegl; do
printf ' %-22s %s\n' "$t" "$(command -v "$t" || echo MISSING)"
done
printf 'corpus: %s\n' "$CORPUS"
printf ' install apps: sudo dnf install -y libreoffice-writer libreoffice-calc inkscape gegl04-tools\n'
printf 'governor: %s (pinned for timed runs; set GOVERNOR=... to choose)\n' "$GOVERNOR" printf 'governor: %s (pinned for timed runs; set GOVERNOR=... to choose)\n' "$GOVERNOR"
local need; need="$(pkgs_for_missing)" printf 'corpus: %s\n' "$CORPUS"
if [ -z "${need// /}" ]; then
printf 'dnf: all installable tools present\n' # --- dnf-provisioned tools and apps ---
printf '\ntools (dnf-provisioned):\n'
local pair cmd pkg missing_pkgs=""
for pair in $PKG_FOR_TOOL_CMD; do
cmd="${pair%%:*}"; pkg="${pair#*:}"
if have "$cmd"; then printf ' %-14s ok\n' "$cmd"
else printf ' %-14s MISSING -> %s\n' "$cmd" "$pkg"; missing_pkgs="$missing_pkgs $pkg"; fi
done
printf 'apps (dnf-provisioned):\n'
for pair in $PKG_FOR_APP_CMD; do
cmd="${pair%%:*}"; pkg="${pair#*:}"
if have "$cmd"; then printf ' %-14s ok\n' "$cmd"
else printf ' %-14s MISSING -> %s\n' "$cmd" "$pkg"; missing_pkgs="$missing_pkgs $pkg"; fi
done
# --- bundled binaries / data (not dnf) ---
printf 'bundled:\n'
if [ -x "$PT" ]; then printf ' %-14s ok (%s)\n' PassMark "$PT"
else printf ' %-14s MISSING -> see README section 4\n' PassMark; problems+=("PassMark binary not found at $PT"); fi
if [ -x "$LLAMA_BIN" ]; then printf ' %-14s ok\n' llama-bench
else printf ' %-14s MISSING -> see README section 4\n' llama-bench; problems+=("llama-bench not found at $LLAMA_BIN"); fi
if [ -f "$LLAMA_MODEL" ]; then printf ' %-14s ok (%s)\n' model "$(du -h "$LLAMA_MODEL" 2>/dev/null | cut -f1)"
else printf ' %-14s MISSING -> see README section 4\n' model; problems+=("GGUF model not found at $LLAMA_MODEL"); fi
if have python3 && python3 -c 'import matplotlib' >/dev/null 2>&1; then printf ' %-14s ok\n' matplotlib
else printf ' %-14s MISSING -> python3-matplotlib (graphs)\n' matplotlib; missing_pkgs="$missing_pkgs python3-matplotlib"; fi
# --- verdict ---
if [ -z "${missing_pkgs// /}" ]; then
printf '\ndnf: nothing missing\n'
else else
printf 'dnf: missing:%s\n' "$need" printf '\ndnf: needed:%s\n' "$missing_pkgs"
printf ' install with: INSTALL_DEPS=1 sudo dnf install -y%s\n' "$need" printf ' install: sudo dnf install -y%s\n' "$missing_pkgs"
printf ' (or auto-provision: INSTALL_DEPS=1 ./run-benchmarks.sh <tag> headless)\n'
fi fi
printf 'headless part needs: sudo (power/governor/turbostat) + a GGUF model for the LLM test.\n'
printf 'headed part needs: a graphical session + glmark2/vkmark.\n' if [ ${#problems[@]} -eq 0 ] && [ -z "${missing_pkgs// /}" ] && [ ${#warnings[@]} -eq 0 ]; then
printf '\nRESULT: ready - all benchmark dependencies are present.\n'
else
printf '\nRESULT: not fully ready:\n'
for p in "${problems[@]}"; do printf ' - missing dependency: %s\n' "$p"; done
[ -n "${missing_pkgs// /}" ] && printf ' - installable via dnf:%s\n' "$missing_pkgs"
for w in "${warnings[@]}"; do printf ' - %s\n' "$w"; done
fi
printf '\nnotes: headless part needs sudo (power/governor/turbostat); headed part needs a graphical session.\n'
} }
# --- sampler + cleanup -------------------------------------------------------- # --- sampler + cleanup --------------------------------------------------------