#!/usr/bin/env bash # Print one frame of the top CPU-consuming processes, then exit. # # Single-shot by design: SysStats re-runs it every procInterval while the CPU # drawer is open (Process.running re-armed by a Timer, like dfProc). Exiting # per frame guarantees stdout is flushed — a long-running loop kept its frame # delimiter stuck in bash's block buffer and the QML side never saw a full # frame. discover.sh is the reference for the bash-script + parse pattern. # # IMPORTANT: the flake wrapper only puts bash + coreutils on PATH, so this uses # ONLY bash builtins and coreutils (nproc, sort, head, sleep, cat) — no awk, no # getconf. Keep it that way or add the tool to runtimeInputs in flake.nix. # # Output: one "P " line per process, highest CPU first. # cpu% is top-style: 100 == one fully-used core. intv="${1:-2}" n="${2:-6}" ncpu="$(nproc 2>/dev/null || echo 1)" pg_kb=4 # page size is 4096B on all targeted arches (x86_64/aarch64) # MemTotal in kB, read without awk. memkb=0 while read -r key val _; do if [ "$key" = "MemTotal:" ]; then memkb="$val"; break; fi done < /proc/meminfo # Aggregate CPU jiffies: sum of the fields on the leading "cpu" line. cpu_total() { local line f s=0 read -r line < /proc/stat # shellcheck disable=SC2086 set -- $line # collapses the double space; $1 == "cpu" shift for f in "$@"; do s=$(( s + f )); done echo "$s" } # Read pid -> (utime+stime) jiffies and rss pages into the given assoc arrays. # /proc//stat is "PID (comm) STATE ...": strip through ") " so comm spaces # and parens can't shift field positions, then utime=14, stime=15, rss=24. snapshot() { local -n _jif="$1" _rss="$2" local f l pid rest for f in /proc/[0-9]*/stat; do # 2>/dev/null first so a process vanishing mid-scan is silent. IFS= read -r l 2>/dev/null < "$f" || continue pid="${l%% *}" rest="${l#*) }" # shellcheck disable=SC2086 set -- $rest _jif["$pid"]=$(( ${12:-0} + ${13:-0} )) _rss["$pid"]="${22:-0}" done } declare -A j1 j2 rss t1="$(cpu_total)" snapshot j1 rss sleep "$intv" t2="$(cpu_total)" snapshot j2 rss dt=$(( t2 - t1 )) [ "$dt" -gt 0 ] || exit 0 # Emit " " for each process seen in both snapshots, sort by cpu # desc, take the top n, then resolve name + mem% for just those. for pid in "${!j2[@]}"; do [ -n "${j1[$pid]:-}" ] || continue d=$(( ${j2[$pid]} - ${j1[$pid]} )) cpu=$(( 100 * ncpu * d / dt )) printf '%d %s %s\n' "$cpu" "$pid" "${rss[$pid]:-0}" done | sort -rn | head -n "$n" | while read -r cpu pid r; do name="$(cat "/proc/$pid/comm" 2>/dev/null)" [ -n "$name" ] || name="$pid" mem=0 [ "$memkb" -gt 0 ] && mem=$(( r * pg_kb * 100 / memkb )) printf 'P %s %s %s\n' "$cpu" "$mem" "$name" done