Skip to content

Instantly share code, notes, and snippets.

@jhyland87
Last active July 6, 2018 16:28
Show Gist options
  • Save jhyland87/45f7690450b9a28df704f1bd094840d3 to your computer and use it in GitHub Desktop.
Save jhyland87/45f7690450b9a28df704f1bd094840d3 to your computer and use it in GitHub Desktop.
Command to launch a cputhrottle command for specific app
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
}
@jhyland87
Copy link
Author

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:
screen shot 2018-07-03 at 7 59 41 am

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment