FocusedWolf

Arch: TestCpu [CPU stress test with journalctl events included in output]

Jul 12th, 2025 (edited)
1,011
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 10.75 KB | None | 0 0
  1. #!/bin/bash
  2.  
  3. # Version 26
  4.  
  5. # WARNING: This script will run a CPU stress test on the processor resulting in high temperatures and power draw.
  6. #          Prolonged usage may cause system instability, degradation, or damage.
  7. #          Do not leave the system unattended during use.
  8. #          Do not run this script unless adequate cooling is in place and you understand the risks.
  9. #          You assume all responsibility for using this script.
  10.  
  11. # Installation:
  12. #     $ sudo cp ./testcpu /usr/local/bin/
  13. #     $ sudo chmod +x /usr/local/bin/testcpu
  14.  
  15. # ----- Colors -----
  16. BLACK="\033[30m"
  17. RED="\033[31m"
  18. GREEN="\033[32m"
  19. YELLOW="\033[33m"
  20. BLUE="\033[34m"
  21. MAGENTA="\033[35m"
  22. CYAN="\033[36m"
  23. WHITE="\033[37m"
  24. BRIGHT_BLACK="\033[90m"
  25. BRIGHT_RED="\033[91m"
  26. BRIGHT_GREEN="\033[92m"
  27. BRIGHT_YELLOW="\033[93m"
  28. BRIGHT_BLUE="\033[94m"
  29. BRIGHT_MAGENTA="\033[95m"
  30. BRIGHT_CYAN="\033[96m"
  31. BRIGHT_WHITE="\033[97m"
  32.  
  33. # ----- Styles -----
  34. RESET="\033[0m" # Reset all styles.
  35. BOLD="\033[1m"
  36. HALF_BRIGHT="\033[2m"
  37. ITALIC="\033[3m"
  38. UNDERLINE="\033[4m"
  39. REVERSED="\033[7m" # Reverse foreground/background.
  40.  
  41. show_help() {
  42.     cat << EOF
  43. Usage: ${0##*/} [OPTIONS] [CPU_LIST]
  44.  
  45. Options:
  46.     -h, --help           Show this help message and exit.
  47.     -m, --mode MODE      Set test mode: 'async' or 'round' (default: async).
  48.                          Accepts formats: -m async, --mode async, or --mode=async
  49.     -c, --cycles NUM     Set the number of test cycles to run (default: 1).
  50.                          Accepts formats: -c 5, --cycles 5, or --cycles=5
  51.     --layout             Display the Logical/Physical CPU layout table and exit.
  52.  
  53. CPU_LIST:
  54.     Optional comma-separated list of logical CPUs or CPU ranges to test.
  55.     Examples: 0-3, 1,3,5
  56.  
  57. Example:
  58.   ${0##*/} --cycles=3 10,11
  59.  
  60. Note: Terminal history is your friend. Press up arrow if you want to re-run the last command.
  61. EOF
  62. }
  63.  
  64. show_layout() {
  65.     declare -A seen
  66.     printf "\n%-5s | %-5s | %-10s\n" "CPU" "Core" "Type"
  67.     echo "------------------------"
  68.  
  69.     while IFS=',' read -r cpu core; do
  70.         if [[ -z ${seen[$core]} ]]; then
  71.             seen[$core]=1
  72.             printf "%-5s | %-5s | %-10s\n" "$cpu" "$core" "Physical"
  73.         else
  74.             printf "%-5s | %-5s | %-10s\n" "$cpu" "$core" "Logical"
  75.         fi
  76.     done < <(lscpu -p=CPU,Core | grep -v '^#')
  77.     exit 0
  78. }
  79.  
  80. parse_cpu_list() {
  81.     local input=$1
  82.     local -a list=()
  83.     local -a parts
  84.     local part start end i
  85.  
  86.     IFS=',' read -ra parts <<< "$input"
  87.  
  88.     for part in "${parts[@]}"; do
  89.         if [[ $part =~ ^([0-9]+)-([0-9]+)$ ]]; then
  90.             start=${BASH_REMATCH[1]}
  91.             end=${BASH_REMATCH[2]}
  92.             if (( start > end )); then
  93.                 echo "Invalid CPU range: $part"
  94.                 exit 1
  95.             fi
  96.             for ((i=start; i<=end; i++)); do list+=("$i"); done
  97.         elif [[ $part =~ ^[0-9]+$ ]]; then
  98.             list+=("$part")
  99.         else
  100.             echo "Invalid CPU list format: $part"
  101.             exit 1
  102.         fi
  103.     done
  104.  
  105.     # Properly sort and remove duplicates
  106.     mapfile -t cpus < <(printf "%s\n" "${list[@]}" | sort -nu)
  107. }
  108.  
  109. # Preserve original arguments before parsing/shifting
  110. original_args=("$@")
  111.  
  112. # Defaults
  113. run_mode="async"
  114. max_test_cycles=1
  115. cpu_list_input=""
  116.  
  117. # If no arguments are specified, show help and exit
  118. if [[ "$#" -eq 0 ]]; then
  119.     show_help
  120.     exit 0
  121. fi
  122.  
  123. # Parse arguments
  124. while [[ "$#" -gt 0 ]]; do
  125.     case $1 in
  126.         -h|--help) show_help; exit 0 ;;
  127.         --layout) show_layout ;;
  128.         -m|--mode) run_mode="${2,,}"; shift ;;
  129.         --mode=*) run_mode="${1#*=}"; run_mode="${run_mode,,}" ;;
  130.         -c|--cycles) max_test_cycles="$2"; shift ;;
  131.         --cycles=*) max_test_cycles="${1#*=}" ;;
  132.         -*) echo "Unknown parameter: $1"; exit 1 ;;
  133.         *) cpu_list_input="$1" ;;
  134.     esac
  135.     shift
  136. done
  137.  
  138. # Check requirements
  139. if ! command -v stress-ng >/dev/null 2>&1; then
  140.     echo -e "\e[94m::\e[0m stress-ng not found. Install with $ sudo pacman -S stress-ng"
  141.     exit 1
  142. fi
  143.  
  144. if ! [[ "$max_test_cycles" =~ ^[0-9]+$ ]] || (( max_test_cycles < 1 )); then
  145.     echo "ERROR: Cycles must be a positive integer greater than 0."
  146.     exit 1
  147. fi
  148.  
  149. # Only request sudo if the script is actually proceeding to run a test
  150. if [[ $EUID -ne 0 ]]; then
  151.     echo "Requesting administrative privileges . . ."
  152.     exec sudo "$0" "${original_args[@]}"
  153. fi
  154.  
  155. # Get total logical CPUs.
  156. max_cpu=$(( $(nproc) - 1 ))
  157.  
  158. # Determine CPU indexes to test.
  159. if [[ -n "$cpu_list_input" ]]; then
  160.     parse_cpu_list "$cpu_list_input"
  161.     cpu_warning="logical CPUs: $(IFS=,; echo "${cpus[*]}")"
  162. else
  163.     cpus=($(seq 0 "$max_cpu"))
  164.     cpu_warning="all logical CPUs"
  165. fi
  166.  
  167. # Validate logical CPUs are within range.
  168. for cpu in "${cpus[@]}"; do
  169.     if (( cpu < 0 || cpu > max_cpu )); then
  170.         echo "ERROR: CPU number out of range: $cpu"
  171.         exit 1
  172.     fi
  173. done
  174.  
  175. while true; do
  176.     echo
  177.     echo -e "${RED}:: WARNING: This script will run a CPU stress test on $cpu_warning resulting in high temperatures and power draw.${RESET}"
  178.     echo -e "${RED}            Prolonged usage may cause system instability, degradation, or damage.${RESET}"
  179.     echo -e "${RED}            Do not leave the system unattended during use.${RESET}"
  180.     echo -e "${RED}            Do not continue unless adequate cooling is in place and you understand the risks.${RESET}"
  181.     echo -e "${RED}            You assume all responsibility for using this script.${RESET}"
  182.     echo
  183.     read -p "Do you want to continue? [Y/N]: " response
  184.     case "$response" in
  185.         y|Y) break ;;
  186.         n|N) exit 0 ;;
  187.         *) echo ;;
  188.     esac
  189. done
  190. echo
  191.  
  192. while true; do
  193.     read -p "Do you want to include a RAM test? [Y/N]: " ram_response
  194.     case "$ram_response" in
  195.         y|Y) include_ram_test=true; break ;;
  196.         n|N) include_ram_test=false; break ;;
  197.         *) echo ;;
  198.     esac
  199. done
  200. echo
  201.  
  202. # Pass cpus array as string to embedded script.
  203. cpus_str="$(IFS=,; echo "${cpus[*]}")"
  204.  
  205. # Create a temporary file.
  206. stress_script=$(mktemp)
  207.  
  208. cleanup_main() {
  209.     [[ -n "$stress_script" ]] && rm -f "$stress_script"
  210. }
  211. trap cleanup_main SIGINT SIGTERM SIGHUP SIGQUIT EXIT
  212.  
  213. # Embedded Script Generation
  214. cat > "$stress_script" <<EOF
  215. #!/bin/bash
  216.  
  217. blue="\e[94m::\e[0m"
  218. yellow="\e[93m::\e[0m"
  219. red="\e[91m::\e[0m"
  220.  
  221. color_stress_output() {
  222.     awk -v c_pass="$BRIGHT_GREEN" -v c_fail="$RED" -v c_reset="$RESET" '
  223.     /failed:/ {
  224.         n = split(\$0, a, "failed: ")
  225.         if (n > 1) {
  226.             val = +a[2]
  227.             if (val > 0) {
  228.                 print c_fail \$0 c_reset
  229.                 next
  230.             } else {
  231.                 print c_pass \$0 c_reset
  232.                 next
  233.             }
  234.         }
  235.     }
  236.     { print }
  237.     '
  238. }
  239.  
  240. cleanup() {
  241.     local exit_code=\$?
  242.     [[ -n "\${stress_pid}" && "\${stress_pid}" =~ ^[0-9]+\$ ]] && kill "\${stress_pid}" 2>/dev/null
  243.     [[ -n "\${journal_pid}" ]] && kill "\${journal_pid}" 2>/dev/null
  244.     exit "\$exit_code"
  245. }
  246. trap cleanup SIGINT SIGTERM SIGHUP SIGQUIT EXIT
  247.  
  248. cpus_str="\$1"
  249. run_mode="\$2"
  250. test_type="\$3"
  251. time_per_test="\$4"
  252. max_test_cycles="\$5"
  253.  
  254. # Default values.
  255. seconds_between_tests=3
  256. max_ram_usage=90
  257.  
  258. # Convert HH:MM:SS to seconds.
  259. IFS=: read -r hh mm ss <<< "\$time_per_test"
  260. seconds_per_test=\$((10#\$hh * 3600 + 10#\$mm * 60 + 10#\$ss))
  261.  
  262. IFS=',' read -ra cpus <<< "\$cpus_str"
  263. num_cpus=\${#cpus[@]}
  264.  
  265. # Launch interleaved journalctl stream unconditionally
  266. journalctl -fk -o short-iso | sed -e "s/^/$(echo -e "${YELLOW}")[SYS LOG] /" -e "s/\$/$(echo -e "${RESET}")/" &
  267. journal_pid=\$!
  268.  
  269. cycle_count=1
  270. while true; do
  271.     if (( cycle_count > max_test_cycles )); then exit 0; fi
  272.  
  273.     if [[ "\$run_mode" == "async" ]]; then
  274.         eta=\$(printf '%02d:%02d:%02d' \$((seconds_per_test / 3600)) \$(((seconds_per_test % 3600) / 60)) \$((seconds_per_test % 60)))
  275.         progress=\$((cycle_count * 100 / max_test_cycles))
  276.         echo -e "\${yellow} Running async stress test on CPUs: \$cpus_str [Cycle \${cycle_count}/\${max_test_cycles}, Progress: \${progress}% ETA: \${eta}]"
  277.  
  278.         max_ram_per_thread=\$(( max_ram_usage / num_cpus ))
  279.  
  280.         # 1. Create an empty array to hold the PIDs
  281.         stress_pids=()
  282.  
  283.         for cpu in "\${cpus[@]}"; do
  284.             taskset -c "\$cpu" stress-ng \
  285.                 \$( [[ "\$test_type" == "cpu" ]] && echo "--cpu 1 --cpu-method all" ) \
  286.                 \$( [[ "\$test_type" == "ram" ]] && echo "--vm 1 --vm-bytes \${max_ram_per_thread}% --vm-method all --verify --vm-keep" ) \
  287.                 \$( [[ "\$test_type" == "all" ]] && echo "--cpu 1 --cpu-method all --vm 1 --vm-bytes \${max_ram_per_thread}% --vm-method all --verify --vm-keep" ) \
  288.                 --metrics-brief --timeout "\${seconds_per_test}s" 2>&1 | color_stress_output &
  289.  
  290.             # 2. Capture the PID of the newly launched stress-ng process
  291.             stress_pids+=(\$!)
  292.         done
  293.  
  294.         # 3. Wait ONLY for the targeted stress-ng processes to finish
  295.         wait "\${stress_pids[@]}"
  296.  
  297.         echo -e "\${blue} Cooling down for \${seconds_between_tests} seconds . . ."
  298.         sleep "\${seconds_between_tests}"
  299.  
  300.     else
  301.         for idx in "\${!cpus[@]}"; do
  302.             cpu="\${cpus[\$idx]}"
  303.             time_remaining=\$(( (num_cpus - idx - 1) * seconds_per_test ))
  304.             progress=\$(( idx * 100 / num_cpus ))
  305.             eta=\$(printf '%02d:%02d:%02d' \$((time_remaining / 3600)) \$(((time_remaining % 3600) / 60)) \$((time_remaining % 60)))
  306.  
  307.             echo -e "\${yellow} Testing CPU \${cpu} [Cycle \${cycle_count}/\${max_test_cycles}, Progress: \${progress}% ETA: \${eta}]"
  308.  
  309.             taskset -c "\$cpu" stress-ng \
  310.                 \$( [[ "\$test_type" == "cpu" ]] && echo "--cpu 1 --cpu-method all" ) \
  311.                 \$( [[ "\$test_type" == "ram" ]] && echo "--vm 1 --vm-bytes \$max_ram_usage% --vm-method all --verify --vm-keep" ) \
  312.                 \$( [[ "\$test_type" == "all" ]] && echo "--cpu 1 --cpu-method all --vm 1 --vm-bytes \$max_ram_usage% --vm-method all --verify --vm-keep" ) \
  313.                 --metrics-brief --timeout "\${seconds_per_test}s" 2>&1 | color_stress_output &
  314.  
  315.             stress_pid=\$!
  316.             wait "\$stress_pid"
  317.             unset stress_pid
  318.  
  319.             echo -e "\${blue} Cooling down for \${seconds_between_tests} seconds . . ."
  320.             sleep "\${seconds_between_tests}"
  321.         done
  322.     fi
  323.     echo -e "\${red} Completed \${cycle_count} of \${max_test_cycles} test cycles . . ."
  324.     ((cycle_count++))
  325. done
  326.  
  327. [[ -n "\${journal_pid}" ]] && kill "\${journal_pid}" 2>/dev/null
  328. EOF
  329.  
  330. chmod +x "$stress_script"
  331.  
  332. if [[ "$include_ram_test" == "true" ]]; then test_type="all"; else test_type="cpu"; fi
  333. time_per_test="00:00:15"
  334.  
  335. # Execute normally in foreground
  336. "$stress_script" "$cpus_str" "$run_mode" "$test_type" "$time_per_test" "$max_test_cycles"
Advertisement
Comments
Add Comment
Please, Sign In to add comment