hyprdots/Configs/.local/share/bin/gpuinfo.sh
2024-12-10 23:54:56 +01:00

295 lines
12 KiB
Bash
Executable file

#!/usr/bin/env bash
# shellcheck disable=SC2312
# shellcheck disable=SC1090
scrDir=`dirname "$(realpath "$0")"`
gpuQ="/tmp/hyprdots-${UID}-gpuinfo-query"
tired=false
[[ " $* " =~ " tired " ]] && ! grep -q "tired" "${gpuQ}" && echo "tired=true" >>"${gpuQ}"
if [[ ! " $* " =~ " startup " ]]; then
gpuQ="${gpuQ}$2"
fi
detect() { # Auto detect Gpu used by Hyprland(declared using env = WLR_DRM_DEVICES) Sophisticated?
card=$(echo "${WLR_DRM_DEVICES}" | cut -d':' -f1 | cut -d'/' -f4)
# shellcheck disable=SC2010
slot_number=$(ls -l /dev/dri/by-path/ | grep "${card}" | awk -F'pci-0000:|-card' '{print $2}')
vendor_id=$(lspci -nn -s "${slot_number}" )
declare -A vendors=(["10de"]="nvidia" ["8086"]="intel" ["1002"]="amd")
for vendor in "${!vendors[@]}"; do
if [[ ${vendor_id} == *"${vendor}"* ]]; then
initGPU="${vendors[${vendor}]}"
break
fi
done
if [[ -n ${initGPU} ]]; then
$0 --use "${initGPU}" startup
fi
}
query() {
nvidia_flag=0 amd_flag=0 intel_flag=0
touch "${gpuQ}"
if lsmod | grep -q 'nouveau'; then
echo "nvidia_gpu=\"Linux\"" >>"${gpuQ}" #? Incase If nouveau is installed
echo "nvidia_flag=1 # Using nouveau an open-source nvidia driver" >>"${gpuQ}"
elif command -v nvidia-smi &> /dev/null; then
nvidia_gpu=$(nvidia-smi --query-gpu=gpu_name --format=csv,noheader,nounits | head -n 1)
if [[ -n "${nvidia_gpu}" ]] ; then # Check for NVIDIA GPU
if [[ "${nvidia_gpu}" == *"NVIDIA-SMI has failed"* ]]; then #? Second Layer for dGPU
echo "nvidia_flag=0 # NVIDIA-SMI has failed" >> "${gpuQ}"
else
nvidia_address=$(lspci | grep -Ei "VGA|3D" | grep -i "${nvidia_gpu/NVIDIA /}" | cut -d' ' -f1)
{ echo "nvidia_address=\"${nvidia_address}\""
echo "nvidia_gpu=\"${nvidia_gpu/NVIDIA /}\""
echo "nvidia_flag=1"
} >> "${gpuQ}"
fi
fi
fi
if lspci -nn | grep -E "(VGA|3D)" | grep -iq "1002"; then
amd_gpu="$(lspci -nn | grep -Ei "VGA|3D" | grep -m 1 "1002" | awk -F'Advanced Micro Devices, Inc. ' '{gsub(/ *\[[^\]]*\]/,""); gsub(/ *\([^)]*\)/,""); print $2}')"
amd_address=$(lspci | grep -Ei "VGA|3D" | grep -i "${amd_gpu}" | cut -d' ' -f1)
{ echo "amd_address=\"${amd_address}\""
echo "amd_flag=1" # Check for Amd GPU
echo "amd_gpu=\"${amd_gpu}\""
} >> "${gpuQ}";fi
if lspci -nn | grep -E "(VGA|3D)" | grep -iq "8086"; then
intel_gpu="$(lspci -nn | grep -Ei "VGA|3D" | grep -m 1 "8086" | awk -F'Intel Corporation ' '{gsub(/ *\[[^\]]*\]/,""); gsub(/ *\([^)]*\)/,""); print $2}')"
intel_address=$(lspci | grep -Ei "VGA|3D" | grep -i "${intel_gpu}" | cut -d' ' -f1)
{ echo "intel_address=\"${intel_address}\""
echo "intel_flag=1" # Check for Intel GPU
echo "intel_gpu=\"${intel_gpu}\""
} >>"${gpuQ}"; fi
if ! grep -q "prioGPU=" "${gpuQ}" && [[ -n "${WLR_DRM_DEVICES}" ]]; then
trap detect EXIT
fi
}
toggle() {
if [[ -n "$1" ]]; then
next_prioGPU="$1_flag"
else
# Initialize gpu_flags and prioGPU if they don't exist
if ! grep -q "gpu_flags=" "${gpuQ}"; then
gpu_flags=$(grep "flag=1" "${gpuQ}" | cut -d '=' -f 1 | tr '\n' ' ' | tr -d '#')
echo "" >> "${gpuQ}"
echo "gpu_flags=\"${gpu_flags[*]}\"" >> "${gpuQ}"
fi
if ! grep -q "prioGPU=" "${gpuQ}"; then
gpu_flags=$(grep "gpu_flags=" "${gpuQ}" | cut -d'=' -f 2)
initGPU=$(echo "${gpu_flags}" | cut -d ' ' -f 1)
echo "prioGPU=${initGPU}" >> "${gpuQ}"
fi
mapfile -t anchor < <(grep "flag=1" "${gpuQ}" | cut -d '=' -f 1)
prioGPU=$(grep "prioGPU=" "${gpuQ}" | cut -d'=' -f 2) # Get the current prioGPU from the file
# Find the index of the current prioGPU in the anchor array
for index in "${!anchor[@]}"; do
if [[ "${anchor[${index}]}" = "${prioGPU}" ]]; then
current_index=${index}
fi
done
next_index=$(( (current_index + 1) % ${#anchor[@]} ))
next_prioGPU=${anchor[${next_index}]#\#}
fi
# Set the next prioGPU and remove the '#' character
sed -i 's/^\(nvidia_flag=1\|amd_flag=1\|intel_flag=1\)/#\1/' "${gpuQ}" # Comment out all the gpu flags in the file
sed -i "s/^#${next_prioGPU}/${next_prioGPU}/" "${gpuQ}" # Uncomment the next prioGPU in the file
sed -i "s/prioGPU=${prioGPU}/prioGPU=${next_prioGPU}/" "${gpuQ}" # Update the prioGPU in the file
}
# Thee shalt find the greatest one,
# He who not more than the chosen one
map_floor() {
# From the depths of the string, words arise,
# Keys in pairs, a treasure in disguise.
IFS=', ' read -r -a pairs <<< "$1"
# If the final token stands alone and bold,
# Declare it the default, its worth untold.
if [[ ${pairs[-1]} != *":"* ]]; then
def_val="${pairs[-1]}"
unset 'pairs[${#pairs[@]}-1]'
fi
# Scans the map, a peak it seeks,
# The highest passed, the value speaks.
for pair in "${pairs[@]}"; do
IFS=':' read -r key value <<< "$pair"
# Behold! Thou holds the secrets they seek,
# Declare it and silence the whispers unique.
if awk -v num="$2" -v k="$key" 'BEGIN { exit !(num > k) }'; then
echo "$value"
return
fi
done
# On this lonely shore, where silence dwells
# Even the waves, echoes words unheard
[ -n "$def_val" ] && echo $def_val || echo " "
}
# generate emoji and icon based on temperature and utilization
get_icons() {
# key-value pairs of temperature and utilization levels
temp_lv="85:&🌋, 65:&🔥, 45:&☁️, &❄️"
util_lv="90:, 60:󰓅, 30:󰾅, 󰾆"
# return comma separated emojis/icons
icons=$(map_floor "$temp_lv" $1 | sed "s/&/,/")
icons="$icons,$(map_floor "$util_lv" $2)"
echo $icons
}
generate_json() {
# get emoji and icon based on temperature and utilization
icons=$(get_icons "$temperature" "$utilization")
thermo=$(echo $icons | awk -F, '{print $1}')
emoji=$(echo $icons | awk -F, '{print $2}')
speedo=$(echo $icons | awk -F, '{print $3}')
# emoji=$(get_temperature_emoji "${temperature}")
local json="{\"text\":\"${thermo} ${temperature}°C\", \"tooltip\":\"${primary_gpu}\n${thermo} Temperature: ${temperature}°C ${emoji}"
#? Soon Add Something incase needed.
declare -A tooltip_parts
if [[ -n "${utilization}" ]]; then tooltip_parts["\n$speedo Utilization: "]="${utilization}%" ; fi
if [[ -n "${current_clock_speed}" ]] && [[ -n "${max_clock_speed}" ]]; then tooltip_parts["\n Clock Speed: "]="${current_clock_speed}/${max_clock_speed} MHz" ; fi
if [[ -n "${gpu_load}" ]]; then tooltip_parts["\n$speedo Utilization: "]="${gpu_load}%" ; fi
if [[ -n "${core_clock}" ]]; then tooltip_parts["\n Clock Speed: "]="${core_clock} MHz" ;fi
if [[ -n "${power_usage}" ]]; then if [[ -n "${power_limit}" ]]; then
tooltip_parts["\n󱪉 Power Usage: "]="${power_usage}/${power_limit} W"
else
tooltip_parts["\n󱪉 Power Usage: "]="${power_usage} W"
fi
fi
if [[ -n "${power_discharge}" ]] && [[ "${power_discharge}" != "0" ]]; then tooltip_parts["\n Power Discharge: "]="${power_discharge} W" ;fi
for key in "${!tooltip_parts[@]}"; do
local value="${tooltip_parts[${key}]}"
if [[ -n "${value}" && "${value}" =~ [a-zA-Z0-9] ]]; then
json+="${key}${value}"
fi
done
json="${json}\"}"
echo "${json}"
}
general_query() { # Function to get temperature from 'sensors'
filter=''
temperature=$(sensors | ${filter} grep -m 1 -E "(edge|Package id.*|another keyword)" | awk -F ':' '{print int($2)}') #! We can get json data from sensors too
# gpu_load=$()
# core_clock=$()
for file in /sys/class/power_supply/BAT*/power_now; do
[[ -f "${file}" ]] && power_discharge=$(awk '{print $1*10^-6 ""}' "${file}") && break
done
[[ -z "${power_discharge}" ]] && for file in /sys/class/power_supply/BAT*/current_now; do
[[ -e "${file}" ]] && power_discharge=$(awk -v current="$(cat "${file}")" -v voltage="$(cat "${file/current_now/voltage_now}")" 'BEGIN {print (current * voltage) / 10^12 ""}') && break
done
# power_limit=$()
utilization=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1" "}')
current_clock_speed=$(awk '{sum += $1; n++} END {if (n > 0) print sum / n / 1000 ""}' /sys/devices/system/cpu/cpufreq/policy*/scaling_cur_freq)
max_clock_speed=$(awk '{print $1/1000}' /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq)
}
intel_GPU() { #? Function to query basic intel GPU
primary_gpu="Intel ${intel_gpu}"
general_query
}
nvidia_GPU() { #? Function to query Nvidia GPU
primary_gpu="NVIDIA ${nvidia_gpu}"
if [[ "${nvidia_gpu}" == "Linux" ]]; then general_query ; return ; fi #? Open source driver
#? Tired Flag for not using nvidia-smi if GPU is in suspend mode.
if ${tired}; then is_suspend="$(cat /sys/bus/pci/devices/0000:"${nvidia_address}"/power/runtime_status)"
if [[ ${is_suspend} == *"suspend"* ]]; then
printf '{"text":"󰤂", "tooltip":"%s ⏾ Suspended mode"}' "${primary_gpu}"; exit ;fi
fi
gpu_info=$(nvidia-smi --query-gpu=temperature.gpu,utilization.gpu,clocks.current.graphics,clocks.max.graphics,power.draw,power.limit --format=csv,noheader,nounits)
# Split the comma-separated values into an array
IFS=',' read -ra gpu_data <<< "${gpu_info}"
# Extract individual values
temperature="${gpu_data[0]// /}"
utilization="${gpu_data[1]// /}"
current_clock_speed="${gpu_data[2]// /}"
max_clock_speed="${gpu_data[3]// /}"
power_usage="${gpu_data[4]// /}"
power_limit="${gpu_data[5]// /}"
}
amd_GPU() { #? Function to query amd GPU
primary_gpu="AMD ${amd_gpu}"
# Execute the AMD GPU Python script and use its output
amd_output=$(python3 ${scrDir}/amdgpu.py)
if [[ ! ${amd_output} == *"No AMD GPUs detected."* ]] && [[ ! ${amd_output} == *"Unknown query failure"* ]]; then
# Extract GPU Temperature, GPU Load, GPU Core Clock, and GPU Power Usage from amd_output
temperature=$(echo "${amd_output}" | jq -r '.["GPU Temperature"]' | sed 's/°C//')
gpu_load=$(echo "${amd_output}" | jq -r '.["GPU Load"]' | sed 's/%//')
core_clock=$(echo "${amd_output}" | jq -r '.["GPU Core Clock"]' | sed 's/ GHz//;s/ MHz//')
power_usage=$(echo "${amd_output}" | jq -r '.["GPU Power Usage"]' | sed 's/ Watts//')
else
general_query
fi
}
if [[ ! -f "${gpuQ}" ]]; then
query ; echo -e "Initialized Variable:\n$(cat "${gpuQ}")\n\nReboot or '$0 --reset' to RESET Variables"
fi
source "${gpuQ}"
case "$1" in
"--toggle"|"-t")
toggle
echo -e "Sensor: ${next_prioGPU} GPU" | sed 's/_flag//g'
exit
;;
"--use"|"-u")
toggle "$2"
;;
"--reset"|"-rf")
rm -fr "${gpuQ}"*
query
echo -e "Initialized Variable:\n$(cat "${gpuQ}" || true)\n\nReboot or '$0 --reset' to RESET Variables"
exit
;;
*"-"*)
gpu_flags=$(grep "flag=1" "${gpuQ}" | cut -d '=' -f 1 | tr '\n' ' ' | tr -d '#')
cat << EOF
Available GPU: ${gpu_flags//_flag/}
[options]
--toggle * Toggle available GPU
--use [GPU] * Only call the specified GPU (Useful for adding specific GPU on waybar)
--reset * Remove & restart all query
[flags]
tired * Adding this option will not query nvidia-smi if gpu is in suspend mode
startup * Useful if you want a certain GPU to be set at startup
* If ${USER} declared env = WLR_DRM_DEVICES on hyprland then use this as the primary GPU
EOF
exit
;;
esac
nvidia_flag=${nvidia_flag:-0} intel_flag=${intel_flag:-0} amd_flag=${amd_flag:-0}
#? Based on the flags, call the corresponding function multi flags means multi GPU.
if [[ "${nvidia_flag}" -eq 1 ]]; then
nvidia_GPU
elif [[ "${amd_flag}" -eq 1 ]]; then
amd_GPU
elif [[ "${intel_flag}" -eq 1 ]]; then
intel_GPU
else primary_gpu="Not found"
general_query
fi
generate_json #? AutoGen the Json txt for Waybar