Last active
July 6, 2018 16:28
-
-
Save jhyland87/45f7690450b9a28df704f1bd094840d3 to your computer and use it in GitHub Desktop.
Command to launch a cputhrottle command for specific app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function stfu { | |
if [[ ${BASH_VERSINFO} -lt 4 ]]; then | |
echo "[ERROR] You're running Bash v${BASH_VERSINFO}?? Grow some balls and upgrade to v4" 1>&2 | |
return 1 | |
fi | |
declare -A font | |
font['rst']=$(echo -en "\e[0m") | |
font['bld']=$(echo -en "\e[1m") | |
font['red']=$(echo -en "\e[31m") | |
font['red.bld']=${font['red']}${font['bld']} | |
font['gry']=$(echo -en "\e[38m") | |
font['gry.bld']=${font['gry']}${font['bld']} | |
font['wht']=$(echo -en "\e[37m") | |
font['wht.bld']=${font['wht']}${font['bld']} | |
font['grn']=$(echo -en "\e[32m") | |
font['grn.bld']=${font['grn']}${font['bld']} | |
function _error { | |
echo -e "${font['red.bld']}ERROR:${font['rst']} $*" 1>&2 | |
} | |
which -s cputhrottle | |
if [[ $? -ne 0 ]]; then | |
_error "Package cputhrottle not found. Install it using brew" | |
return 1 | |
fi | |
function _help { | |
echo "Available applications: ${font['wht.bld']}${!apps[*]}${font['rst']}" 1>&2 | |
echo -e "\nUsage Examples" | |
echo -e "\n ${font['gry']}Throttle Outlook CPU (default of 20%):${font['rst']}\n ${font['gry.bld']}\$ ${font['wht.bld']}stfu outlook${font['rst']}" | |
echo -e "\n ${font['gry']}Throttle Outlook CPU to 25%:${font['rst']}\n ${font['gry.bld']}\$ ${font['wht.bld']}stfu outlook 25${font['rst']}" | |
echo -e "\n ${font['gry']}Throttle Outlook and watch:${font['rst']}\n ${font['gry.bld']}\$ ${font['wht.bld']}stfu outlook -w${font['rst']}" | |
echo -e "\n ${font['gry']}Throttle Outlook CPU to 40% and watch:${font['rst']}\n ${font['gry.bld']}\$ ${font['wht.bld']}stfu outlook -w 40${font['rst']}" | |
echo | |
return | |
} | |
unset cfg | |
unset app | |
unset status | |
unset apps | |
declare -A cfg=([throttle]=20 [obsess]=0) | |
declare -A app | |
declare -A status=([attempts]=0 [passes]=0) | |
declare -A apps | |
# Application names and bundle IDs | |
apps["outlook"]="com.microsoft.Outlook" | |
apps["iterm"]="com.googlecode.iterm2" | |
apps["sqlpro"]="com.sequelpro.SequelPro" | |
apps["virtualbox"]="org.virtualbox.app.VirtualBox" | |
apps["mamp"]="de.appsolute.MAMP" | |
apps["slack"]="com.tinyspeck.slackmacgap" | |
apps["sublime"]="com.sublimetext.2" | |
apps["chrome"]="com.google.Chrome" | |
# Aliases | |
apps["iterm2"]=${apps["iterm"]} | |
apps["sublime2"]=${apps["sublime"]} | |
apps["googlechrome"]=${apps["chrome"]} | |
apps["vbox"]=${apps["virtualbox"]} | |
for arg in "$@"; do | |
# If help is requested for any of the arguments, display it and quit | |
if [[ ${arg} == "-h" ]] || [[ ${arg} == "-help" ]] || [[ ${arg} == "-?" ]]; then | |
_help | |
return | |
fi | |
# If -w is set (watch/obsess) | |
if [[ ${arg} == "-w" ]] || [[ ${arg} == "-watch" ]] || [[ ${arg} == "-o" ]] || [[ ${arg} == "-obsess" ]]; then | |
cfg[obsess]=1 | |
continue | |
fi | |
# A numerical value will be the CPU throttle limit | |
if [[ ! -z "${arg##*[!0-9]*}" ]]; then | |
cfg[throttle]="${arg}" | |
continue | |
fi | |
# if an app was already specified, then abort | |
if [[ -n ${app[name]} ]]; then | |
echo "More than 1 app specified" 1>&2 | |
return 1 | |
fi | |
arglc=$(echo "${arg}" | tr '[A-Z]' '[a-z]') | |
# If the app value provided exists in the apps array as a key, then use that | |
if [[ -n ${apps[${arglc}]} ]]; then | |
app[name]="${arglc}" | |
continue | |
else | |
# This should search the applications matching the application name OR bundle ID | |
appbundle=$(lsappinfo info "${arg}" -only bundleID | cut -d= -f2 | tr -d '"' 2>/dev/null) | |
# If lsappinfo failed.. | |
if [[ $? -ne 0 ]]; then | |
_error "Failed to get the bundle ID of an application matching '${arg}'" | |
return 1 | |
fi | |
# If no application was found... | |
if [[ -z ${appbundle} ]]; then | |
_error "No application found when searching for '${arg}'" | |
return 1 | |
fi | |
# If an app was found, set the app name and add the application name and bundle to the apps array | |
app[name]="${arglc}" | |
apps["${arglc}"]="${appbundle}" | |
continue | |
fi | |
# If this arg is in the apps array.. then handle it as the app | |
if [[ -n ${apps[${arglc}]} ]]; then | |
app[name]="${arglc}" | |
continue | |
fi | |
done | |
if [[ -z ${app[name]} ]]; then | |
echo -e "${font['red.bld']}ERROR:${font['rst']} Application name invalid or not provided\n" 1>&2 | |
_help | |
return 1 | |
fi | |
app[bundle]=${apps[${app[name]}]} | |
app[pid]=$(lsappinfo info "${app[bundle]}" -only pid | cut -d '=' -f2) | |
app[cpu]=$(ps -o %cpu -p ${app[pid]} | sed 1d | tr -d ' ') | |
printf_fmt="${font['gry']}%10s${font['rst']} -> ${font['wht.bld']}%s${font['rst']}\n" | |
printf "${printf_fmt}" "name" "${app[name]}" | |
printf "${printf_fmt}" "bundle" "${app[bundle]}" | |
printf "${printf_fmt}" "pid" "${app[pid]}" | |
printf "${printf_fmt}" "cpu" "${app[cpu]}%" | |
echo | |
existing_throttle=$(ps -a -o pid,ppid,comm,args | awk -v pid=${app[pid]} '{ m = match($0, sprintf("[c]puthrottle %i", pid)); if(m != 0) print $1 }' | tr '\n' ' ') | |
if [[ -n ${existing_throttle} ]]; then | |
echo "Existing cputhrottle for pid ${app[pid]} already running (pids: ${existing_throttle})" | |
echo | |
echo -n "Killing cputhrottle commands... " | |
sudo kill -9 ${existing_throttle} >/dev/null | |
if [[ $? -ne 0 ]]; then | |
echo "Failed" | |
return 1 | |
fi | |
echo "Done" | |
echo | |
fi | |
echo -n "Limiting the CPU utilization of ${font['wht.bld']}${app[name]}${font['rst']} (PID ${font['wht.bld']}${app[pid]}${font['rst']}) to ${font['wht.bld']}${cfg[throttle]}%${font['rst']}... " | |
sudo cputhrottle ${app[pid]} ${cfg[throttle]} & disown | |
if [[ ${cfg[obsess]} -eq 0 ]]; then | |
return | |
fi | |
echo -e "\nWatching CPU utilization of PID ${app[pid]}...\n" | |
declare -A watcher=([sleep]=0.5 [limit]=40 [okchecks]=3) | |
printf "%5s %-10s %s %8s\n" "#" "DATE" "%CPU" "ATTEMPT" | |
while true; do | |
status[checks]=$((${status[checks]}+1)) | |
app[cpu]=$(ps -o %cpu -p ${app[pid]} | sed 1d) | |
load_ok=$(echo "${app[cpu]} < ${cfg[throttle]}" | bc -l) | |
if [[ ${load_ok} -eq 1 ]]; then | |
status[passes]=$((${status[passes]}+1)) | |
cpu_clr="${font['grn.bld']}" | |
else | |
status[passes]=0 | |
cpu_clr="${font['red.bld']}" | |
fi | |
if [[ ${status[passes]} -eq ${watcher[okchecks]} ]]; then | |
suffix_col="${font['wht.bld']}" | |
suffix="${status[passes]}/${watcher[okchecks]}" | |
elif [[ ${status[passes]} -gt 0 ]]; then | |
suffix_col="${wht}" | |
suffix="${status[passes]}/${watcher[okchecks]}" | |
else | |
suffix_col="${font['gry']}" | |
suffix="-" | |
fi | |
printf "${font['gry']}%5s${font['rst']} | ${font['gry']}%-10s${font['rst']} ${cpu_clr}%s${font['rst']} ${suffix_col}%8s${font['rst']}\n" ${status[checks]} "$(date +%T)" "${app[cpu]}" "${suffix}" | |
if [[ ${status[passes]} -eq ${watcher[okchecks]} ]]; then | |
echo | |
echo "CPU utilization throttled to below ${font['wht.bld']}${cfg[throttle]}%${font['rst']} and verified by ${font['wht.bld']}${status[passes]} consecutive checks${font['rst']}" | |
break | |
fi | |
if [[ ${status[checks]} -eq ${watcher[limit]} ]]; then | |
echo "Aborting loop after ${status[checks]} iterations" | |
break | |
fi | |
sleep ${watcher[sleep]} | |
done | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This version allows you to watch the processes CPU usage after the limit is established, which will wait for the processes CPU usage to lower below the threshold before it exits. Screenshot:
