Last active
November 25, 2025 13:43
-
-
Save JaseHadd/64567f103e3481a556426c98f3f83a4d to your computer and use it in GitHub Desktop.
macOS App Memory Usage Checker
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
| #!/bin/zsh | |
| # This file can be included in your .zshrc, and then you can invoke it as 'app-mem', | |
| # or you can save this file anywhere on your path, make it executable, and run it from | |
| # any shell. | |
| # The included function will gather all the processes for a given app and total up their | |
| # memory usage, so you can check at a glance how much memory something is using. | |
| app-mem() | |
| { | |
| local SUCCESS=0 USAGE_ERROR=1 NO_APP_FOUND=2 NO_PROCESSES=3 INTERNAL_ERROR=4 | |
| local flag_verbose flag_help flag_appname flag_bundle flag_processname flag_safari | |
| local count identifier | |
| local verbose=false | |
| _APP_MEM_BASE_NAME=${_APP_MEM_BASE_NAME:-app-mem} | |
| show_usage() | |
| { | |
| cat << EOF | |
| Usage: $_APP_MEM_BASE_NAME [OPTIONS] | |
| $_APP_MEM_BASE_NAME -h | |
| Options: | |
| -a, --app-name {identifier} Query by Application Name (e.g., Visual Studio Code). This uses the Spotlight | |
| index to locate the application, and will find the same application as | |
| 'open -a identifier'. | |
| -b, --bundle {identifier} Query by Bundle Identifier (e.g., com.microsoft.VSCode). This uses the spotlight | |
| index to locate the application, and will find the same application as | |
| 'open -b identifier'. | |
| -p, --process-name {identifier} Query by Process Name (e.g., Code). This searches the running processes for any | |
| that match the provided name, and is the least precise method. | |
| -s, --safari Query Safari browser processes. This is the only accurate way to get memory usage for | |
| Safari, as Safari uses multiple processes for tabs and extensions. | |
| -v, --verbose Enable verbose output | |
| -h, --help Show this help message | |
| EOF | |
| } | |
| zmodload zsh/zutil | |
| if ! zparseopts -D -F -M -K -- \ | |
| {v,-verbose}=flag_verbose \ | |
| {h,-help}=flag_help \ | |
| {a,-app-name}=flag_appname \ | |
| {b,-bundle}=flag_bundle \ | |
| {p,-process-name}=flag_processname \ | |
| {s,-safari}=flag_safari; then | |
| show_usage | |
| return $USAGE_ERROR | |
| fi | |
| if [[ -n "$flag_help" ]]; then | |
| show_usage | |
| return $SUCCESS | |
| fi | |
| # number of modes specified | |
| count=$(( ${#flag_appname} + ${#flag_bundle} + ${#flag_processname} + ${#flag_safari} )) | |
| # convert verbose flag to boolean | |
| [[ -n $flag_verbose ]] && verbose=true | |
| if [[ $count -eq 0 ]]; then | |
| # default to app name mode | |
| flag_appname=true | |
| fi | |
| if [[ $count -gt 1 ]] \ | |
| || ( [[ $# -eq 0 ]] && [[ -z "$flag_safari" ]] ) \ | |
| || ( [[ $# -ne 0 ]] && [[ -n "$flag_safari" ]] ); then | |
| # exit if multiple modes specified, or if incorrect number of arguments for the selected mode | |
| show_usage | |
| return $USAGE_ERROR | |
| else | |
| identifier="$@" | |
| fi | |
| format_kib() | |
| { | |
| local kib=$1 | |
| local number unit | |
| bc -l << EOF | read unit number | |
| input=$kib | |
| gib = 1024 * (mib = 1024) | |
| if (input < mib) { print "KiB "; i = input; } | |
| else if (input < gib) { print "MiB "; i = input / mib; } | |
| else { print "GiB "; i = input / gib; } | |
| if (i < 10) { scale = 2; i / 1; } | |
| else if (i < 100) { scale = 1; i / 1; } | |
| else { scale = 0; i / 1; } | |
| EOF | |
| echo "$number $unit" | |
| } | |
| recurse_pid() | |
| { | |
| while read pid; do | |
| echo "$pid" | |
| ps -ax -oppid=,pid= \ | |
| | awk -v parent="$pid" '$1 == parent { print $2 }' | |
| done | |
| } | |
| print_results() | |
| { | |
| local kib entries memory | |
| local identifier="$1" | |
| awk '{ print $1 }' | recurse_pid \ | |
| | while read pid; do; ps -o rss= -p "$pid" 2>/dev/null; done \ | |
| | awk '{sum+=$1; count++} END {print sum, count}' \ | |
| | read kib entries | |
| if [[ $entries -eq 0 ]]; then | |
| echo "No running processes found for '$identifier'" | |
| return $NO_PROCESSES | |
| fi | |
| memory=$(format_kib $kib) | |
| if [[ $verbose = true ]]; then | |
| echo "Found $entries process(es) for '$identifier'" | |
| echo "Total Memory Usage: $memory" | |
| else | |
| echo "$memory" | |
| fi | |
| } | |
| if [ -n "$flag_appname" ]; then | |
| local appPath | |
| identifier="${identifier%.app}.app" | |
| $verbose && echo "Searching by Application Name: $identifier" | |
| appPath=$(mdfind "kMDItemKind == 'Application' && kMDItemFSName == '$identifier'" | head -n 1) | |
| if [ -z "$appPath" ]; then | |
| echo "No application found with name '$identifier'" | |
| return $NO_APP_FOUND | |
| fi | |
| ps -ax -opid=,comm= | grep -i "$appPath" | print_results $identifier | |
| return $? | |
| elif [ -n "$flag_bundle" ]; then | |
| $verbose && echo "Searching by Bundle Identifier: $identifier" | |
| local appPath | |
| appPath=$(mdfind "kMDItemCFBundleIdentifier == '$identifier'" | head -n 1) | |
| if [ -z "$appPath" ]; then | |
| echo "No application found with bundle identifier '$identifier'" | |
| return $NO_APP_FOUND | |
| fi | |
| ps -ax -opid=,comm= | grep -i "$appPath" | print_results $identifier | |
| return $? | |
| elif [ -n "$flag_processname" ]; then | |
| $verbose && echo "Searching by Process Name: $identifier" | |
| ps -caxm -opid=,comm= | grep -i "$identifier" | print_results $identifier | |
| return $? | |
| elif [ -n "$flag_safari" ]; then | |
| $verbose && echo "Querying Safari processes" | |
| local asns=() | |
| local pids=() | |
| for asn in $(lsappinfo find displayname="Safari Networking"); do | |
| asns+=("$asn") | |
| asns+=($(lsappinfo find parentasn="$asn")) | |
| done | |
| asns+=($(lsappinfo find displayname="Safari Graphics and Media")) | |
| asns+=($(lsappinfo find bundleid="com.apple.Safari")) | |
| print -l -- $asns \ | |
| | xargs -n 1 lsappinfo info -only pid \ | |
| | awk -F'=' '{ print $2 }' \ | |
| | print_results "Safari" | |
| return $? | |
| else | |
| echo "Internal error: No valid mode selected" | |
| return $INTERNAL_ERROR | |
| fi | |
| } | |
| if [[ "${ZSH_EVAL_CONTEXT}" == "toplevel" ]]; then | |
| _APP_MEM_BASE_NAME=$(basename "$0") | |
| app-mem "$@" | |
| exit $? | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment