#!/bin/bash
# mini-benchmarker
# by torvic9
# contributors and testers:
# Richard Gladman, William Pursell, SGS, mbb, mbod, Manjaro Forum, StackOverflow
# and everyone else I forgot
#
# In Memoriam Jonathon Fernyhough

TNAMES=('stress-ng cpu-cache-mem'	#0
	'ffmpeg compilation'		#1
	'x265 encoding'			#2
	'argon2 hashing'		#3
	'perf sched msg fork thread'	#4
	'perf memcpy'			#5
	'calculating prime numbers'	#6
	'namd 92K atoms'		#7
	'blender render'		#8
	'xz compression'		#9
	'kernel defconfig'		#10
	'y-cruncher pi 1b'		#11
	'schbench p99 latency (us)'	#12
	'cyclictest max latency (us)'	#13
)

# check if the active scheduler is still running and hasn't crashed/exited
check_scheduler_integrity() {
	if [[ "$SCX" != "none" ]]; then
		if [ -f "/sys/kernel/sched_ext/root/ops" ]; then
			local CURRENT_OPS
			CURRENT_OPS=$(cat "/sys/kernel/sched_ext/root/ops" 2>/dev/null || true)
			if [ -z "$CURRENT_OPS" ]; then
				return 1
			fi
			local CURRENT_SCX
			CURRENT_SCX=$(echo "$CURRENT_OPS" | awk -F'[[:digit:]]' '{print $1}' | sed -rn 's/^(.*)_$/\1/p')
			[[ -z "$CURRENT_SCX" ]] && CURRENT_SCX="$CURRENT_OPS"
			if [[ "$CURRENT_SCX" != "$SCX" ]]; then
				return 1
			fi
		else
			return 1
		fi
	fi
	return 0
}

# animation
animate() {
	local s='-+' ; local i=0
	while kill -0 "$PID" &>/dev/null
	do
		if ! check_scheduler_integrity; then
			printf "\n${FARBE1}${TB}**** CRITICAL: Scheduler crashed or exited during ${TNAMES[$1]}! ****${TN}\n"
			kill -9 "$PID" 2>/dev/null || true
			echo -e "${TNAMES[$1]}: FAILED (Scheduler crashed)" >> "$LOGFILE"
			echo -e "\nSCX Scheduler: ${SCX} (CRASHED during ${TNAMES[$1]})" >> "$LOGFILE"
			exit 9
		fi
		i=$(( (i+1) %2 ))
		printf "\b${s:$i:1}"
		sleep 1
	done
	printf "\b " ; cat "$RESFILE"
	echo -e "${TNAMES[$1]}: $(cat "$RESFILE")" >> "$LOGFILE"
}

ensure_archive() {
	local file="$1"
	local description="$2"
	local url="$3"
	shift 3

	if [[ -f "$file" ]] && ! "$@" "$file" &>/dev/null; then
		echo "--> Cached ${description} archive is invalid, re-downloading..."
		rm -f "$file"
	fi

	if [[ ! -f "$file" ]]; then
		echo "--> Downloading ${description} archive..."
		wget -c --show-progress -qO "$file" "$url"
	fi
}

# tests
runstress() {
	local RESFILE="$WORKDIR/runstress"
	/usr/bin/time -f %e -o "$RESFILE" stress-ng -q --job "$WORKDIR/stressC" &>/dev/null &
	local PID=$!
	echo -n -e "* ${TNAMES[0]}:\t\t"
	animate 0 && return 0 || return 99
}

runffm() {
	cd "$WORKDIR/ffmpeg-$FFMPEGVER" || exit 4
	local RESFILE="$WORKDIR/runffm"
	/usr/bin/time -f %e -o "$RESFILE" make -s -j${CPUCORES} &>/dev/null &
	local PID=$!
	echo -n -e "* ${TNAMES[1]}:\t\t\t"
	animate 1 && return 0 || return 99
}

runx265() {
	local RESFILE="$WORKDIR/runx265"
	/usr/bin/time -f %e -o "$RESFILE" x265 -p slow -b 6 -o /dev/null \
	  --no-progress --log-level none --input "$WORKDIR/bosphorus_hd.y4m" &
	local PID=$!
	echo -n -e "* ${TNAMES[2]}:\t\t\t"
	animate 2 && return 0 || return 99
}

runargon() {
	local RESFILE="$WORKDIR/runargon"
	/usr/bin/time -f %e -o "$RESFILE" argon2 BenchieSalt -id -t 20 -m 21 \
	  -p "$CPUCORES" &>/dev/null <<< $(head -c 64 /dev/urandom) &
	local PID=$!
	echo -n -e "* ${TNAMES[3]}:\t\t\t"
	animate 3 && return 0 || return 99
}

runperf_sch() {
	local RESFILE="$WORKDIR/runperfs"
	perf bench -f simple sched messaging -t -g 24 -l 6000 1> "$RESFILE" &
	local PID=$!
	echo -n -e "* ${TNAMES[4]}:\t\t"
	animate 4 && return 0 || return 99
}

runperf_mem() {
	local RESFILE="$WORKDIR/runperfm"
	/usr/bin/time -f %e -o "$RESFILE" perf bench -f simple mem memcpy --nr_loops 100 \
	  --size 2GB -f default &>/dev/null &
	local PID=$!
	echo -n -e "* ${TNAMES[5]}:\t\t\t\t"
	animate 5 && return 0 || return 99
}

runprime() {
	local RESFILE="$WORKDIR/runprime"
	/usr/bin/time -f%e -o "$RESFILE" primesieve 666000000000 --no-status | awk -F ': ' \
	  '/Seconds/{print $2}' 1> "$RESFILE" &
	local PID=$!
	echo -n -e "* ${TNAMES[6]}:\t\t"
	animate 6 && return 0 || return 99
}

runnamd() {
	cd "$WORKDIR/namd/NAMD_3.0b6_Linux-x86_64-multicore" || exit 4
	local RESFILE="$WORKDIR/runnamd"
	rm -f ../apoa1/FFTW*.txt
	/usr/bin/time -f%e -o "$RESFILE" ./namd3 +p${CPUCORES} +setcpuaffinity \
	  ../apoa1/apoa1.namd &>/dev/null &
	local PID=$!
	echo -n -e "* ${TNAMES[7]}:\t\t\t"
	animate 7 && return 0 || return 99
}

runblend() {
	local RESFILE="$WORKDIR/runblend"
	local BLENDER_USER_CONFIG="$WORKDIR"
	/usr/bin/time -f %e -o "$RESFILE" blender -b "$WORKDIR/bmw_cpu_mod.blend" \
	  -o "$WORKDIR/blenderbmw.jpg" -f 1 --verbose 0 -t ${CPUCORES} &>/dev/null &
	local PID=$!
	echo -n -e "* ${TNAMES[8]}:\t\t\t"
	animate 8 && return 0 || return 99
}

runxz() {
	local RESFILE="$WORKDIR/runxz"
	/usr/bin/time -f %e -o "$RESFILE" xz -z -k -T${CPUCORES} -Qqq \
	  -f "$WORKDIR/firefox102.tar" &
	local PID=$!
	echo -n -e "* ${TNAMES[9]}:\t\t\t"
	animate 9 && return 0 || return 99
}

runkern() {
	cd "$WORKDIR/linux-$KERNVER" || exit 4
	local RESFILE="$WORKDIR/runkern"
	/usr/bin/time -f %e -o "$RESFILE" make KCFLAGS='-Wno-error' -sj$CPUCORES vmlinux &>/dev/null &
	local PID=$!
	echo -n -e "* ${TNAMES[10]}:\t\t\t"
	animate 10 && return 0 || return 99
}

runyc() {
	cd "$WORKDIR/y-cruncher v$YCRUNCHER_VER-static" || exit 4
	rm -f "Pi*.txt"
	local RESFILE="$WORKDIR/runyc"
	/usr/bin/time -f%e -o "$RESFILE" ./y-cruncher bench 1b -od:0 \
	  -o "$WORKDIR" &>/dev/null &
	local PID=$!
	echo -n -e "* ${TNAMES[11]}:\t\t\t"
	animate 11 && return 0 || return 99
}

runschbench() {
	local RESFILE="$WORKDIR/runschbench"
	schbench -m 2 -r 30 2>&1 | tee "$WORKDIR/schbench_raw" | \
	  grep -oP '99\.0th:\s*\K\d+' | head -1 | \
	  awk '{printf "%.2f\n", $1}' > "$RESFILE" &
	local PID=$!
	echo -n -e "* ${TNAMES[12]}:\t\t"
	animate 12 && return 0 || return 99
}

runrps() {
	local RESFILE="$WORKDIR/runrps"
	if [[ -f "$WORKDIR/schbench_raw" ]]; then
		grep -oP 'average rps: \K[\d.]+' "$WORKDIR/schbench_raw" | \
		  awk '{printf "%.2f\n", $1}' > "$RESFILE"
	fi
	echo -n -e "* schbench avg rps:\t\t"
	cat "$RESFILE" 2>/dev/null || echo "N/A"
	return 0
}

runcyclictest() {
	local RESFILE="$WORKDIR/runcyclictest"
	cyclictest -D 30 -t -i 1000 -d 0 -q -m --policy=other 2>/dev/null | \
	  grep '^T:' | sed 's/.*Max:[[:space:]]*//' | sort -n | tail -1 | \
	  awk '{printf "%.2f\n", $1}' > "$RESFILE" &
	local PID=$!
	echo -n -e "* ${TNAMES[13]}:\t\t"
	animate 13 && return 0 || return 99
}

rungeekbench() {
	GB_STATUS="failed"
	GB_ERROR=""
	GB_URL=""
	GB_LOG="$WORKDIR/geekbench_${CDATE}.log"

	geekbench --cpu > "$GB_LOG" 2>&1 &
	local PID=$!
	echo -n -e "* geekbench CPU benchmark:\t\t"
	local s='-+' ; local i=0
	while kill -0 "$PID" &>/dev/null
	do i=$(( (i+1) %2 ))
		printf "\b${s:$i:1}"
		sleep 1
	done

	wait "$PID"
	local gb_rc=$?
	if (( gb_rc != 0 )); then
		printf "\b failed\n"
		GB_ERROR="Geekbench failed with exit code $gb_rc"
		return 0
	fi

	printf "\b done\n"
	GB_STATUS="completed"
	GB_URL=$(grep -oE 'https://browser\.geekbench\.com/v6/cpu/[0-9]+' "$GB_LOG" | head -n1)
	if [[ -n "$GB_URL" ]]; then
		GB_STATUS="uploaded"
	else
		GB_ERROR="Geekbench completed, but no result URL was found"
	fi
	return 0
}

# intro text and explanation
intro() {
    # ANSI escape sequences for formatting
    local B_CYAN="\033[1;36m"
    local B_BLUE="\033[1;34m"
    local B_RED="\033[1;31m"
    local B_GREEN="\033[1;32m"
    local B_YELLOW="\033[1;33m"
    local BOLD="\033[1m"
    local REGULAR="\033[0m"

    echo -e "\n  ${B_BLUE}┌────────────────────────────────────────────────────────┐${REGULAR}"
    echo -e "  ${B_BLUE}│${B_CYAN}${BOLD}                   MINI-BENCHMARKER                     ${B_BLUE}│${REGULAR}"
    echo -e "  ${B_BLUE}└────────────────────────────────────────────────────────┘${REGULAR}"
    echo -e "  ${B_RED}${BOLD}Warning: This script can take a very long time on slow computers!${REGULAR}\n"

    echo -e "  ${B_CYAN}${BOLD}Usage:${REGULAR}"
    echo -e "    cachyos-benchmarker [options] <workdir>\n"

    echo -e "  ${B_CYAN}${BOLD}Options:${REGULAR}"
    echo -e "    ${B_GREEN}-c, --cleanup${REGULAR}  Delete downloaded archives & extracted trees on exit"
    echo -e "    ${B_GREEN}-h, --help${REGULAR}     Show this help message\n"

    echo -e "  ${B_CYAN}${BOLD}Detected Environment:${REGULAR}"
    echo -e "    ${B_YELLOW}• Host Kernel${REGULAR}  : $(uname -r)"
    if [[ "$SCX" != "none" ]]; then
        echo -e "    ${B_YELLOW}• Sched-Ext${REGULAR}    : ${SCX} (${SCX_VERSION:-unknown version})"
    else
        echo -e "    ${B_YELLOW}• Sched-Ext${REGULAR}    : none"
    fi
    echo -e "    ${B_YELLOW}• CPU Cores${REGULAR}    : ${CPUCORES} cores"
    local RAM_GB
    RAM_GB=$(python -c "print(round($RAMSIZE / 1024, 1))")
    echo -e "    ${B_YELLOW}• System RAM${REGULAR}   : ${RAM_GB} GB (${RAMSIZE} MB)\n"

    echo -e "  ${B_CYAN}${BOLD}Benchmarks Overview — Category 1: Throughput & Compilation${REGULAR}"
    echo -e "    ${B_YELLOW}• stress-ng cpu-cache-mem${REGULAR} : Sort & search, integer/float arithmetics, memory & cache"
    echo -e "    ${B_YELLOW}• perf sched${REGULAR}              : Interprocess communication and pipelining"
    echo -e "    ${B_YELLOW}• perf mem${REGULAR}                : Raw RAM throughput speed using libc memcpy"
    echo -e "    ${B_YELLOW}• primesieve${REGULAR}              : Multithreaded search for primes up to 6.66*10^11"
    echo -e "    ${B_YELLOW}• NAMD${REGULAR}                    : Multithreaded molecular dynamics simulation"
    echo -e "    ${B_YELLOW}• argon2${REGULAR}                  : Hashing algorithm (16 iterations, 2GB memory)"
    echo -e "    ${B_YELLOW}• compilation${REGULAR}             : Compiles FFmpeg and the Linux kernel"
    echo -e "    ${B_YELLOW}• xz compression${REGULAR}          : Compresses Firefox source tarball using xz"
    echo -e "    ${B_YELLOW}• x265 encoding${REGULAR}           : Encodes a short 1080p video clip"
    echo -e "    ${B_YELLOW}• Blender BMW${REGULAR}             : CPU-only Blender render (BMW model)"
    echo -e "    ${B_YELLOW}• y-cruncher${REGULAR}              : Highly optimized pi calculator (1 billion digits)"
    echo -e ""
    echo -e "  ${B_CYAN}${BOLD}Benchmarks Overview — Category 2: Scheduler Latency${REGULAR}"
    echo -e "    ${B_YELLOW}• schbench (us)${REGULAR}          : Wakeup latency benchmark (P99 latency, ↓ lower is better)"
    echo -e "    ${B_YELLOW}• schbench (rps)${REGULAR}         : Wakeup throughput from the same schbench run (↑ higher is better)"
    echo -e "    ${B_YELLOW}• cyclictest (us)${REGULAR}       : Real-time scheduling latency (max latency, ↓ lower is better)"
    echo -e "    ${B_YELLOW}• geekbench (optional)${REGULAR}    : Runs standard CPU benchmarks (requires 'geekbench' in PATH)\n"

    echo -e "  ${B_CYAN}${BOLD}Resilience & Caching Features:${REGULAR}"
    echo -e "    ${B_GREEN}• Asset Caching${REGULAR}       : Downloaded archives (.tar.xz, .7z) and extracted source directories"
    echo -e "                            are cached in <workdir> by default to accelerate repeated runs."
    echo -e "    ${B_GREEN}• Resumable Downloads${REGULAR} : Interrupted downloads automatically resume via 'wget -c'."
    echo -e "    ${B_GREEN}• Integrity Checks${REGULAR}    : Archive files are verified (tar -tf, xz -t, 7z t) before run."
    echo -e "                            Corrupted archives are automatically detected and re-downloaded."
    echo -e "    ${B_GREEN}• Scheduler Monitor${REGULAR}   : Actively monitors the active sched_ext scheduler. If it crashes or"
    echo -e "                            exits mid-run, the benchmarker terminates safely (exits with code 9).\n"

    echo -e "  ${B_CYAN}${BOLD}Exit Codes:${REGULAR}"
    echo -e "    ${B_YELLOW}• 0${REGULAR} : All benchmarks completed successfully"
    echo -e "    ${B_YELLOW}• 1${REGULAR} : Command-line syntax / argument error"
    echo -e "    ${B_YELLOW}• 2${REGULAR} : Interrupted by user (SIGINT) or insufficient RAM (< 4GB)"
    echo -e "    ${B_YELLOW}• 4${REGULAR} : Failed to create/access directories or prepare sources"
    echo -e "    ${B_YELLOW}• 8${REGULAR} : General run or compilation failure"
    echo -e "    ${B_YELLOW}• 9${REGULAR} : Critical: Sched-Ext scheduler crashed or exited mid-run\n"

    echo -e "  ${B_CYAN}${BOLD}Notes:${REGULAR}"
    echo -e "    - Results are logged to individual files: ${BOLD}benchie_<name>_<date>.log${REGULAR} in <workdir>"
    echo -e "    - A comparative HTML report (${BOLD}test_performance.html${REGULAR}) and performance charts"
    echo -e "      (${BOLD}.png${REGULAR}) are automatically compiled in <workdir> at the end of each run"
    echo -e "    - Total Time is the key benchmark metric (score is calculated using geometric mean)"
    echo -e "    - It is highly recommended to run this in runlevel 3 (multi-user target):"
    echo -e "        ${BOLD}systemctl isolate multi-user.target${REGULAR}"
    echo -e "      To return to the GUI:"
    echo -e "        ${BOLD}systemctl isolate graphical.target${REGULAR}\n"
}

# traps (ctrl-c)
killproc() {
	echo -e "\n**** Received SIGINT, aborting! ****\n"
	kill -- -$$ && exit 2
}

exitproc() {
	echo -e "\n-> Removing temporary files..."
	rm -f "$WORKDIR"/run* "$WORKDIR"/stressC "$WORKDIR"/schbench_raw \
	      "$WORKDIR"/*.txt "$WORKDIR"/*.jpg "$WORKDIR"/*.ppm
	rm -f "$LOCKFILE"

	if [[ "$CLEANUP" == "true" ]]; then
		echo -e "-> Cleaning up downloaded archives and extracted directories..."
		rm -f "$WORKDIR"/firefox102.tar.xz "$WORKDIR"/*.zst "$WORKDIR"/*.7z \
		      "$WORKDIR"/ffmpeg.tar.xz "$WORKDIR"/namd.tar.gz "$WORKDIR"/namd-example.tar.gz \
		      "$WORKDIR"/linux-$KERNVER.tar.xz "$WORKDIR"/y-cruncher.tar.xz
		rm -rf "$WORKDIR/ffmpeg-$FFMPEGVER" \
		       "$WORKDIR/namd" \
		       "$WORKDIR/linux-$KERNVER" \
		       "$WORKDIR/y-cruncher v$YCRUNCHER_VER-static" \
		       "$WORKDIR/firefox102.tar" \
		       "$WORKDIR/bosphorus_hd.y4m"
	fi
	echo -e "${TB}Bye!${TN}\n"
}

# vars
export LANG=C
CURRDIR=$(pwd)
SCRIPTDIR=$(dirname "$(readlink -f "$0")")
TMP="/tmp"
VER="v2.2"
FFMPEGVER="7.0.1"
YCRUNCHER_VER="0.8.6.9545"
KERNEL_NAME=$(uname -r)
CDATE=$(date +%F-%H%M)
RAMSIZE=$(awk '/MemTotal/{print int($2 / 1000)}' /proc/meminfo)
CPUCORES=$(nproc)
CPUFREQ=$(awk '{print $1 / 1000000}' /sys/devices/system/cpu/cpufreq/policy0/cpuinfo_max_freq)
COEFF="$(python -c "print(round((($CPUCORES + 1) / 2 * $CPUFREQ / 2) ** (1/3),2))")"
KERNVER="6.14.7"
# system info will be logged
SYSINFO=$(inxi -CmSz -c0 -y-1 | sed -e "s/Array.*:.*//;/^\s*$/d")

# Set defaults as none
SCX="none"
SCX_VERSION=""
# update if we find them below

if [ -f "/sys/kernel/sched_ext/root/ops" ]; then
	TMP=$(cat "/sys/kernel/sched_ext/root/ops")
	if [ -n "$TMP" ]; then
		SCX=$(awk -F'[[:digit:]]' '{print $1}' "/sys/kernel/sched_ext/root/ops" | sed -rn 's/^(.*)_$/\1/p')
		SCX_VERSION=$(sed -rn 's/^[^0-9]*([0-9]+(\.[0-9]+)+).*$/\1/p' "/sys/kernel/sched_ext/root/ops")
		[[ -z "$SCX" ]] && SCX="$TMP"
	fi
fi

VERSION=$(pacman -Qi cachyos-benchmarker 2>/dev/null | grep "Version         :" || true)
# allow more open files, needed by perf bench msg
ulimit -n 4096

# I leave this for reference
#CPUFREQ=$(cpupower frequency-info -l | grep -v "analyzing" | awk '{print $2 / 1000000}')
#CPUGOV=$(cpupower frequency-info -o | grep -m1 "^CPU" | awk -F' -  ' '{ print $3 }')
#CPUMHZ=$(lscpu -e=maxmhz | tail -n1)
#CPUGHZ=$(echo "scale=1; ${CPUMHZ%%,*} / 1000" | bc)
#CPUL3C=$(lscpu -C=name,all-size | awk '/L3/{print $2}')

# terminal effects
TB=$(tput bold)
TN=$(tput sgr0)
FARBE1="`printf '\033[0;91m'`"
FARBE2="`printf '\033[4;37m'`"
FARBE3="`printf '\033[0;33m'`"

CLEANUP=false
WORKDIR=""

while [[ $# -gt 0 ]]; do
	case "$1" in
		-c|--cleanup)
			CLEANUP=true
			shift
			;;
		-h|--help)
			intro
			exit 0
			;;
		-*)
			echo "Unknown option: $1"
			intro
			exit 1
			;;
		*)
			if [[ -z "$WORKDIR" ]]; then
				WORKDIR="$1"
			else
				echo "Too many arguments. Expected: [options] <workdir>"
				exit 1
			fi
			shift
			;;
	esac
done

if [[ -z "$WORKDIR" ]]; then
	intro
	exit 0
fi

# check system memory
[[ $RAMSIZE -lt 3500 ]] && echo "Your computer must have at least 4 GB of RAM! Aborting." && exit 2

[[ "${WORKDIR:0:1}" != "/" ]] && WORKDIR="$PWD/$WORKDIR"
if [[ ! -d "$WORKDIR" ]] ; then
	read -p "The specified directory ${TB}$WORKDIR${TN} does not exist. Create it (y/N)? " DCHOICE
	[[ $DCHOICE = "y" || $DCHOICE = "Y" ]] && mkdir -p "$WORKDIR" || exit 4
fi

# check files function
checkfiles() {
	echo -e "\n${TB}Checking, downloading and preparing test files...${TN}"

	# stress-ng jobfile
	cat > "$WORKDIR/stressC" <<- EOF
	run sequential 0
	no-rand-seed
	temp-path /tmp
	timeout 0
	matrix CPUCORES
	matrix-method prod
	matrix-size 256
	EOF
	echo "matrix-ops $((2400 / ${CPUCORES}))" >> "$WORKDIR/stressC"
	cat >> "$WORKDIR/stressC" <<- EOF
	sparsematrix CPUCORES
	sparsematrix-method hash
	sparsematrix-items 15000
	EOF
	echo "sparsematrix-ops $((2400 / ${CPUCORES}))" >> "$WORKDIR/stressC"
	cat >> "$WORKDIR/stressC" <<- EOF
	shm CPUCORES
	shm-bytes 16m
	EOF
	echo "shm-ops $((2400 / ${CPUCORES}))" >> "$WORKDIR/stressC"
	cat >> "$WORKDIR/stressC" <<- EOF
	fork CPUCORES
	fork-max 8
	EOF
	echo "fork-ops $((24000 / ${CPUCORES}))" >> "$WORKDIR/stressC"
	cat >> "$WORKDIR/stressC" <<- EOF
	cpu CPUCORES
	cpu-method cdouble
	EOF
	echo "cpu-ops $((4800 / ${CPUCORES}))" >> "$WORKDIR/stressC"
	cat >> "$WORKDIR/stressC" <<- EOF
	bsearch CPUCORES
	EOF
	echo "bsearch-ops $((2400 / ${CPUCORES}))" >> "$WORKDIR/stressC"
	cat >> "$WORKDIR/stressC" <<-EOF
	stream CPUCORES
	EOF
	echo "stream-ops $((4800 / ${CPUCORES}))" >> "$WORKDIR/stressC"
	cat >> "$WORKDIR/stressC" <<- EOF
	list CPUCORES
	EOF
	echo "list-ops $((2400 / ${CPUCORES}))" >> "$WORKDIR/stressC"
	cat >> "$WORKDIR/stressC" <<- EOF
	qsort CPUCORES
	qsort-size 65536
	EOF
	echo "qsort-ops $((2400 / ${CPUCORES}))" >> "$WORKDIR/stressC"
	cat >> "$WORKDIR/stressC" <<- EOF
	memfd CPUCORES
	memfd-bytes 128m
	memfd-fds 128
	EOF
	echo "memfd-ops $((2400 / ${CPUCORES}))" >> "$WORKDIR/stressC"

	sed -i "s/CPUCORES/$CPUCORES/g" "$WORKDIR/stressC"

	if [[ ! -f "$WORKDIR/bosphorus_hd.y4m" ]] ; then
		ensure_archive "$WORKDIR/bosphorus_hd.7z" "video" \
		  "http://ultravideo.cs.tut.fi/video/Bosphorus_1920x1080_120fps_420_8bit_YUV_Y4M.7z" \
		  7z t
		echo "--> Unzipping video..."
		cd "$WORKDIR"
		7z e bosphorus_hd.7z -o./ &>/dev/null
		mv Bosphorus_1920x1080_120fps_420_8bit_YUV.y4m bosphorus_hd.y4m
	fi

	if [[ ! -d "$WORKDIR/ffmpeg-$FFMPEGVER" ]]; then
		ensure_archive "$WORKDIR/ffmpeg.tar.xz" "ffmpeg" \
		  "https://ffmpeg.org/releases/ffmpeg-$FFMPEGVER.tar.xz" \
		  tar -tf
		echo "--> Preparing ffmpeg..."
		cd "$WORKDIR"
		tar -xf ffmpeg.tar.xz
	fi

	if [[ ! -d "$WORKDIR/namd" ]]; then
		ensure_archive "$WORKDIR/namd.tar.gz" "NAMD" \
		  "http://www.ks.uiuc.edu/Research/namd/3.0b6/download/120834/NAMD_3.0b6_Linux-x86_64-multicore.tar.gz" \
		  tar -tf
		ensure_archive "$WORKDIR/namd-example.tar.gz" "NAMD example" \
		  "https://www.ks.uiuc.edu/Research/namd/utilities/apoa1.tar.gz" \
		  tar -tf
		echo "--> Preparing NAMD..."
		cd "$WORKDIR"
		mkdir namd
		tar -C namd -xf namd.tar.gz
		tar -C namd -xf namd-example.tar.gz
		sed -i 's/\/usr//;s/500/400/' namd/apoa1/apoa1.namd
	fi

	if [[ ! -f "$WORKDIR/bmw_cpu_mod.blend" ]]; then
		echo "--> Downloading Blender BMW model..."
		wget -c --show-progress -qO "$WORKDIR/bmw_cpu_mod.blend" \
		  https://gitlab.com/torvic9/mini-benchmarker/-/raw/master/bmw_cpu_mod.blend
	fi

	if [[ ! -f "$WORKDIR/firefox102.tar" ]]; then
		ensure_archive "$WORKDIR/firefox102.tar.xz" "Firefox" \
		  "http://ftp.mozilla.org/pub/firefox/releases/102.9.0esr/source/firefox-102.9.0esr.source.tar.xz" \
		  xz -t
		echo "--> Unzipping Firefox tarball..."
		xz -d -k -q -f "$WORKDIR/firefox102.tar.xz"
	fi

	if [[ ! -d "$WORKDIR/linux-$KERNVER" ]]; then
		ensure_archive "$WORKDIR/linux-$KERNVER.tar.xz" "Linux kernel" \
		  "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-$KERNVER.tar.xz" \
		  tar -tf
		echo "--> Uncompressing kernel source..."
		cd "$WORKDIR"
		tar -xf linux-$KERNVER.tar.xz
	fi

	if [[ ! -d "$WORKDIR/y-cruncher v$YCRUNCHER_VER-static" ]]; then
		ensure_archive "$WORKDIR/y-cruncher.tar.xz" "y-cruncher" \
		  "https://github.com/Mysticial/y-cruncher/releases/download/v$YCRUNCHER_VER/y-cruncher.v$YCRUNCHER_VER-static.tar.xz" \
		  tar -tf
		echo "--> Uncompressing y-cruncher..."
		cd "$WORKDIR"
		tar -xf y-cruncher.tar.xz
	fi

	echo -e "\n${TB}Preparing kernel source...${TN}"
	cd "$WORKDIR/linux-$KERNVER" || exit 4
	make -s distclean && make -s defconfig

	echo -e "\n${TB}Preparing ffmpeg source...${TN}"
	cd "$WORKDIR/ffmpeg-$FFMPEGVER" || exit 4
	make -s distclean &>/dev/null
	./configure --prefix=$TMP --disable-debug --enable-static \
	    --enable-gpl --enable-version3 --disable-ffplay --disable-ffprobe \
	    --disable-programs --disable-doc --disable-network --disable-protocols \
	    --disable-filters --disable-iconv --enable-libdrm --disable-stripping \
	    --disable-autodetect --cpu=native &>/dev/null
}

checksys() {
	# ask user for dropping page cache
	echo -e "\n"
	read -p "Do you want to drop page cache now? Root privileges needed! (y/N) " DCHOICE
	[[ $DCHOICE = "y" || $DCHOICE = "Y" ]] && sync && su -c "echo 1 > /proc/sys/vm/drop_caches"

	LOCKFILE=$(mktemp "$WORKDIR/benchie.XXXX")

	read -p "Please enter a name for this run, or leave empty for default: " NCHOICE
	[[ -z $NCHOICE ]] && NCHOICE="$USER@$HOSTNAME-${LOCKFILE#*benchie.}"
	# results will be written to this file
	LOGFILE="$WORKDIR/benchie_${NCHOICE}_${CDATE}.log"

	# traps (ctrl-c)
	trap killproc INT
	trap exitproc EXIT
}

header() {
	echo -e "\n${TB}Starting...${TN}\n" ; sync ; sleep 2

	echo "Kernel: $(uname -r)"
	echo "Sched-Ext: $SCX $SCX_VERSION"
	echo ""

	echo -e "__________________________________________________"
	echo -e "=====${TB}__${TN}==${TB}__${TN} ===========================${TB}_____${TN} ====="
	echo -e "====${TB}|  \/  |${TN}==== MINI BENCHMARKER ====${TB}| ___ ))${TN}===="
	echo -e "====${TB}| |\/| |${TN}======= by torvic9 =======${TB}| ___ \\${TN}====="
	echo -e "====${TB}|_|${TN}==${TB}|_|${TN}=========  $VER  =========${TB}|_____//${TN}===="
	echo -e "==================================================\n"
}

# run
NRTESTS=14
declare -a WEIGHTS=(0.9 0.9 0.85 0.85 0.85 0.85 0.8 0.95 0.95 1 0.95 1 0.85 0.9)
checkfiles && checksys && header || exit 8
runstress && sleep 2 || exit 8
if [[ "$KERNEL_NAME" == *"infinity"* ]]; then
	echo -e "* ${TNAMES[11]}:\t\tSKIPPED (Infinity scheduler: takes >1m)"
	echo -e "${TNAMES[11]}: 0.01" >> "$LOGFILE"
else
	runyc && sleep 2 || exit 8
fi
runperf_sch && sleep 2 || exit 8
runperf_mem && sleep 2 || exit 8
runnamd && sleep 2 || exit 8
runprime && sleep 2 || exit 8
runargon && sleep 2 || exit 8
runffm && sleep 2 || exit 8
runxz && sleep 2 || exit 8
runkern && sleep 2 || exit 8
runblend && sleep 2 || exit 8
runx265 && sleep 2 || exit 8
runschbench && sleep 2 || exit 8
runrps
sleep 1
runcyclictest && sleep 2 || exit 8

# time and score calculations, print and log final results
unset ARRAYTIME ; unset ARRAY
ARRAYTIME=($(awk -F': ' '{print $2}' "$LOGFILE"))

# Guard: replace any empty or zero ARRAYTIME entries with 0.01
# to keep the geometric mean well-defined
for ((i=0; i<${NRTESTS}; i++)) ; do
	if [[ -z "${ARRAYTIME[$i]}" || "${ARRAYTIME[$i]}" == "0" || "${ARRAYTIME[$i]}" == "0.0" || "${ARRAYTIME[$i]}" == "0.00" ]]; then
		ARRAYTIME[$i]=0.01
	fi
done

# using geometric mean for now
for ((i=0; i<${NRTESTS}; i++)) ; do
	ARRAY[$i]="$(python -c "print(round(( ${ARRAYTIME[$i]} * $COEFF * ${WEIGHTS[$i]}),4))")"
done

TOTTIME="$(IFS="+" ; python -c "print(round((${ARRAYTIME[*]}),2))")"
INTSCORE="$(IFS="*" ; python -c "print(round((${ARRAY[*]}),4))")"
SCORE="$(python -c "print(round(($INTSCORE ** (1 / $NRTESTS)),2))")"

echo "--------------------------------------------------"
echo "Total time in seconds:"
echo "--------------------------------------------------"
echo "  ${TB}$TOTTIME${TN}" ; echo -e "\nTotal time (s): $TOTTIME" >> "$LOGFILE"
echo "--------------------------------------------------"
echo -n "Total score, lower is better" ; echo " [multi = $COEFF]:"
echo "--------------------------------------------------"
echo "  ${TB}$SCORE${TN}" ; echo "Total score: $SCORE" >> "$LOGFILE"
echo "=================================================="

# geekbench (score-based, not included in timed scoring)
if command -v geekbench &>/dev/null; then
	rungeekbench
	echo "--------------------------------------------------"
	echo "Geekbench CPU Result:"
	echo "--------------------------------------------------"
	if [[ "$GB_STATUS" == "uploaded" ]]; then
		echo "  Result URL: $GB_URL"
		echo "  Log: $GB_LOG"
		echo -e "geekbench status: uploaded" >> "$LOGFILE"
		echo -e "geekbench url: $GB_URL" >> "$LOGFILE"
		echo -e "geekbench log: $GB_LOG" >> "$LOGFILE"
	else
		echo "  ${FARBE1}${GB_ERROR:-Geekbench did not provide a result URL}${TN}"
		[[ -n "$GB_LOG" ]] && echo "  Log: $GB_LOG"
		echo -e "geekbench status: ${GB_STATUS:-failed}" >> "$LOGFILE"
		[[ -n "$GB_ERROR" ]] && echo -e "geekbench error: $GB_ERROR" >> "$LOGFILE"
		[[ -n "$GB_LOG" ]] && echo -e "geekbench log: $GB_LOG" >> "$LOGFILE"
	fi
	echo "=================================================="
fi

echo -e "schbench avg rps: $(cat "$WORKDIR/runrps" 2>/dev/null || echo "0")" >> "$LOGFILE"
echo -e "\nName: ${NCHOICE}" >> "$LOGFILE"
echo -e "Date: ${CDATE}" >> "$LOGFILE"
echo -e "\n$SYSINFO" >> "$LOGFILE"
echo -e "\nSCX Scheduler: ${SCX}" >> "$LOGFILE"
echo -e "\nSCX Version: ${SCX_VERSION}" >> "$LOGFILE"
echo -e "\n ${VERSION}" >> "$LOGFILE"

cd "$WORKDIR"
if [[ -f "$SCRIPTDIR/benchmark_scraper.py" ]]; then
	python "$SCRIPTDIR/benchmark_scraper.py"
else
	python /usr/bin/benchmark_scraper.py
fi

exit 0
