Created
April 29, 2025 23:13
-
-
Save superhero/5efa1c3b65ad343797acdb7e5f1c19c9 to your computer and use it in GitHub Desktop.
System Process Performance Profile (super process tree)
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/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