Skip to content

Instantly share code, notes, and snippets.

@superhero
Created April 29, 2025 23:13
Show Gist options
  • Save superhero/5efa1c3b65ad343797acdb7e5f1c19c9 to your computer and use it in GitHub Desktop.
Save superhero/5efa1c3b65ad343797acdb7e5f1c19c9 to your computer and use it in GitHub Desktop.
System Process Performance Profile (super process tree)
#!/bin/bash
is_lt() { awk "BEGIN {exit !($1 < $2)}"; }
# System info
total_cpus=$(nproc)
uptime=$(awk '{print $1}' /proc/uptime)
hertz=$(getconf CLK_TCK)
total_mem_kb=$(awk '/MemTotal/ { print $2 }' /proc/meminfo)
docker_ps=$(docker ps --no-trunc --format '{{.ID}} {{.Names}}')
# ANSI colors
BOLD='\033[1m'
GRAY='\033[90m'
RED='\033[1;31m'
YELLOW='\033[1;33m'
RESET='\033[0m'
# Temp file for sorting
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"' EXIT
# Gather data
ps -e -o pid=,ppid= | while read -r pid ppid; do
stat_path="/proc/$pid/stat"
statm_path="/proc/$pid/statm"
[[ ! -r "$stat_path" || ! -r "$statm_path" ]] && continue
read -r -a stat < "$stat_path" || continue
[[ ${#stat[@]} -lt 22 ]] && continue
comm=$(tr -d '()' <<< "${stat[1]}" | tr -dc '[:print:]')
utime=${stat[13]}
stime=${stat[14]}
starttime=${stat[21]}
total_time=$((utime + stime))
lifetime=$(awk -v up="$uptime" -v st="$starttime" -v hz="$hertz" 'BEGIN { print up - (st / hz) }')
awk "BEGIN { exit (${lifetime} < 1) ? 0 : 1 }" && continue
cpu_now=$(ps -p "$pid" -o %cpu= 2>/dev/null | awk '{printf "%.1f", $1}')
cpu_avg=$(awk -v t="$total_time" -v l="$lifetime" -v h="$hertz" -v c="$total_cpus" \
'BEGIN { avg=(t/h)/l*100/c; printf "%.1f", (avg >= 0 ? avg : 0) }')
rss_pages=$(awk '{print $2}' "$statm_path")
rss_kb=$((rss_pages * 4))
mem_pct_raw=$(awk -v r="$rss_kb" -v t="$total_mem_kb" 'BEGIN { print r / t * 100 }')
mem_pct=$(awk -v v="$mem_pct_raw" 'BEGIN { printf "%.1f", v }')
cid=$(grep 'docker' /proc/$pid/cgroup 2>/dev/null | sed 's|.*/docker/||' | head -n1)
cname=$(echo "$docker_ps" | awk -v cid="$cid" '$1 == cid { print $2 }')
tasks=$(echo /proc/$pid/task/* | wc -w)
# Output tab-separated fields for sorting and processing
echo -e "${ppid}\t${pid}\t${comm}\t${cpu_now}\t${cpu_avg}\t${mem_pct}\t$tasks\t${cname}" >> "$tmpfile"
done
# Find max values for highlighting
read max_cpu second_cpu <<<$(awk -F'\t' '{print $4}' "$tmpfile" | sort -nr | awk 'NR==1{max=$1} NR==2{second=$1} END{print max, second}')
read max_avg second_avg <<<$(awk -F'\t' '{print $5}' "$tmpfile" | sort -nr | awk 'NR==1{max=$1} NR==2{second=$1} END{print max, second}')
read max_mem second_mem <<<$(awk -F'\t' '{print $6}' "$tmpfile" | sort -nr | awk 'NR==1{max=$1} NR==2{second=$1} END{print max, second}')
read max_tsk second_tsk <<<$(awk -F'\t' '{print $7}' "$tmpfile" | sort -nr | awk 'NR==1{max=$1} NR==2{second=$1} END{print max, second}')
# Sort tree
awk -F'\t' '
{
pid[$2] = $0
children[$1] = children[$1] $2 " "
}
END { walk(0, 0, "") }
function walk(ppid, depth, prefix, arr, n, i, j, child, new_prefix, branch, tmp) {
n = split(children[ppid], arr, " ")
# Simple bubble-sort
for (i = 1; i <= n; i++) {
for (j = i + 1; j <= n; j++) {
if (arr[i] > arr[j]) {
tmp = arr[i]
arr[i] = arr[j]
arr[j] = tmp
}
}
}
for (i = 1; i <= n; i++) {
child = arr[i]
if (child in pid) {
split(pid[child], fields, "\t")
if (depth == 0) {
# Top level, no prefix
tree_prefix = ""
next_prefix = ""
} else {
# Set tree branch
tree_prefix = (i < n ? "├ " : "└ ")
next_prefix = prefix (i < n ? "│ " : " ")
}
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
fields[1], fields[2], prefix tree_prefix fields[3], fields[4], fields[5], fields[6], fields[7], fields[8]
walk(child, depth + 1, next_prefix)
}
}
}
' "$tmpfile" > "$tmpfile.sorted"
# Calculate maxlen separately
maxlen=$(cut -f3 "$tmpfile.sorted" | awk '{ extra=gsub(/[│├└]/,""); print length + extra }' | sort -nr | head -n1)
# Format
awk -F'\t' \
-v b="$BOLD" -v g="$GRAY" -v r="$RED" -v y="$YELLOW" -v n="$RESET" \
-v mc="$max_cpu" -v ma="$max_avg" -v mm="$max_mem" -v tsk="$max_tsk" \
-v mc2="$second_cpu" -v ma2="$second_avg" -v mm2="$second_mem" -v tsk2="$second_tsk" \
-v maxlen="$maxlen" '
BEGIN {
sum_cpu=0
sum_avg=0
sum_mem=0
sum_tsk=0
fmt = "%-7s %-" maxlen "s %6s %6s %6s %7s %s\n"
printf g fmt n, "PID", "COMMAND", "CPU(%)", "AVG(%)", "MEM(%)", "THREADS", "CONTAINER"
}
{
color = ""
if ($4 == mc || $5 == ma || $6 == mm || $7 == tsk) color = r
else if ($4 == mc2 || $5 == ma2 || $6 == mm2 || $7 == tsk2) color = y
if ($8) color = color b
sum_cpu += $4
sum_avg += $5
sum_mem += $6
sum_tsk += $7
extra = gsub(/[│├└]/, "&", $3)
pad = maxlen + extra - (extra / 3)
printf color "%-7s %-" pad "s %6s %6s %6s %7s %s\n" n, $2, $3, $4, $5, $6, $7, $8
}
END {
printf g fmt n, "", "TOTAL", sum_cpu, sum_avg, sum_mem, sum_tsk, ""
}' "$tmpfile.sorted"
echo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment