diff --git a/Services/SysMonitorService.qml b/Services/SysMonitorService.qml index 97fcf74f..cfb0a696 100644 --- a/Services/SysMonitorService.qml +++ b/Services/SysMonitorService.qml @@ -10,6 +10,10 @@ import qs.Common Singleton { id: root + + readonly property string shellDir: Qt.resolvedUrl(".").toString().replace( + "file://", "").replace("/Services/", "") + property int refCount: 0 property int updateInterval: refCount > 0 ? 3000 : 30000 property int maxProcesses: 100 @@ -598,356 +602,9 @@ Singleton { onTriggered: root.updateAllStats() } -readonly property string staticDataScript: `exec 2>/dev/null - set -o pipefail - json_escape() { sed -e 's/\\\\/\\\\\\\\/g' -e 's/"/\\\\"/g' -e ':a;N;$!ba;s/\\n/\\\\n/g'; } - - printf "{" - - cpu_count=$(nproc) - cpu_model=$(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2- | sed 's/^ *//' | json_escape || echo 'Unknown') - - printf '"cpu":{"count":%d,"model":"%s"},' "$cpu_count" "$cpu_model" - - dmip="/sys/class/dmi/id" - [ -d "$dmip" ] || dmip="/sys/devices/virtual/dmi/id" - mb_vendor=$([ -r "$dmip/board_vendor" ] && cat "$dmip/board_vendor" | json_escape || echo "Unknown") - mb_name=$([ -r "$dmip/board_name" ] && cat "$dmip/board_name" | json_escape || echo "") - bios_ver=$([ -r "$dmip/bios_version" ] && cat "$dmip/bios_version" | json_escape || echo "Unknown") - bios_date=$([ -r "$dmip/bios_date" ] && cat "$dmip/bios_date" | json_escape || echo "") - - kern_ver=$(uname -r | json_escape) - distro=$(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d= -f2- | tr -d '"' | json_escape || echo 'Unknown') - host_name=$(hostname | json_escape) - arch_name=$(uname -m) - - printf '"system":{"kernel":"%s","distro":"%s","hostname":"%s","arch":"%s","motherboard":"%s %s","bios":"%s %s"},' \\ - "$kern_ver" "$distro" "$host_name" "$arch_name" "$mb_vendor" "$mb_name" "$bios_ver" "$bios_date" - - printf '"gpus":[' - gfirst=1 - tmp_gpu=$(mktemp) - - # Map driver -> vendor, else infer from lspci line - infer_vendor() { - case "$1" in - nvidia|nouveau) echo NVIDIA ;; - amdgpu|radeon) echo AMD ;; - i915|xe) echo Intel ;; - *) case "$2" in - *NVIDIA*|*Nvidia*|*nvidia*) echo NVIDIA ;; - *AMD*|*ATI*|*amd*|*ati*) echo AMD ;; - *Intel*|*intel*) echo Intel ;; - *) echo Unknown ;; - esac ;; - esac - } - - # Priority for sorting (nvidia first, then dGPU AMD, then iGPU AMD/Intel) - prio_of() { - local drv="$1" bdf="$2" - case "$drv" in - nvidia) echo 3 ;; - amdgpu|radeon) - # crude: device number from BDF 0000:BB:DD.F -> DD - local dd="\${bdf##*:}"; dd="\${dd%%.*}" - [ "$dd" = "00" ] && echo 1 || echo 2 - ;; - i915|xe) echo 0 ;; - *) echo 0 ;; - esac - } - - # Enumerate all VGA/3D/2D/Display devices (domain-aware) - LC_ALL=C lspci -nnD 2>/dev/null | grep -iE ' VGA| 3D| 2D| Display' | while IFS= read -r line; do - bdf="\${line%% *}" # 0000:BB:DD.F - short_bdf="\${bdf#0000:}" - - # kernel driver in use - drv=""; vendor="Unknown" - if [ -e "/sys/bus/pci/devices/\$bdf/driver" ]; then - drv="$(basename "$(readlink -f "/sys/bus/pci/devices/\$bdf/driver")")" - fi - - vendor="$(infer_vendor "\$drv" "\$line")" - - # Just pass the raw line, we'll parse it in JavaScript - raw_line="$(printf '%s' "\$line" | json_escape)" - - # priority for sorting - prio="$(prio_of "\$drv" "\$bdf")" - - printf '%s|%s|%s|%s\\n' "\$prio" "\$drv" "\$vendor" "\$raw_line" >> "\$tmp_gpu" - done - - # Output JSON - if [ -s "\$tmp_gpu" ]; then - while IFS='|' read -r pr drv vendor raw_line; do - [ \$gfirst -eq 1 ] || printf "," - printf '{"driver":"%s","vendor":"%s","rawLine":"%s"}' \\ - "\$drv" "\$vendor" "\$raw_line" - gfirst=0 - done < <(sort -t'|' -k1,1nr -k2,2 "\$tmp_gpu") - fi - - rm -f "\$tmp_gpu" - printf ']' - - printf "}\\n"` - - readonly property string dynamicDataScript: ` - sort_key=\${1:-cpu} - max_procs=\${2:-20} - collect_gpu_temps=\${3:-0} - collect_nvidia_only=\${4:-0} - - collect_non_nvidia=\${5:-1} - json_escape() { sed -e 's/\\\\/\\\\\\\\/g' -e 's/"/\\\\"/g' -e ':a;N;$!ba;s/\\n/\\\\n/g'; } - - printf "{" - - mem_line="$(awk '/^MemTotal:/{t=$2} - /^MemFree:/{f=$2} - /^MemAvailable:/{a=$2} - /^Buffers:/{b=$2} - /^Cached:/{c=$2} - /^Shmem:/{s=$2} - /^SwapTotal:/{st=$2} - /^SwapFree:/{sf=$2} - END{printf "%d %d %d %d %d %d %d %d",t,f,a,b,c,s,st,sf}' /proc/meminfo)" - read -r MT MF MA BU CA SH ST SF <<< "$mem_line" - printf '"memory":{"total":%d,"free":%d,"available":%d,"buffers":%d,"cached":%d,"shared":%d,"swaptotal":%d,"swapfree":%d},' \\ - "$MT" "$MF" "$MA" "$BU" "$CA" "$SH" "$ST" "$SF" - - # Get pss per pid - get_pss_kb() { - # Read PSS with zero external processes. - # 1) Prefer smaps_rollup (fast, single file) - # 2) Fallback to summing PSS in smaps - # Return 0 if unavailable. - local pid="$1" f total v k _ - f="/proc/$pid/smaps_rollup" - if [ -r "$f" ]; then - # smaps_rollup has one Pss: line — read it directly - while read -r k v _; do - if [ "$k" = "Pss:" ]; then - printf '%s\n' "\${v:-0}" - return - fi - done < "$f" - printf '0\n' - return - fi - f="/proc/$pid/smaps" - if [ -r "$f" ]; then - total=0 - while read -r k v _; do - [ "$k" = "Pss:" ] && total=$(( total + (v:-0) )) - done < "$f" - printf '%s\n' "$total" - return - fi - printf '0\n' - } - - cpu_count=$(nproc) - cpu_model=$(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2- | sed 's/^ *//' | json_escape || echo 'Unknown') - cpu_freq=$(awk -F: '/cpu MHz/{gsub(/ /,"",$2);print $2;exit}' /proc/cpuinfo || echo 0) - cpu_temp=0 - for hwmon_dir in /sys/class/hwmon/hwmon*/; do - [ -d "$hwmon_dir" ] || continue - name_file="\${hwmon_dir}name" - [ -r "$name_file" ] || continue - - # Check if this hwmon is for CPU temperature - if grep -qE 'coretemp|k10temp|k8temp|cpu_thermal|soc_thermal' "$name_file" 2>/dev/null; then - # Look for temperature files without using wildcards in quotes - for temp_file in "\${hwmon_dir}"temp*_input; do - if [ -r "$temp_file" ]; then - cpu_temp=$(awk '{printf "%.1f", $1/1000}' "$temp_file" 2>/dev/null || echo 0) - break 2 # Break both loops - fi - done - fi - done - - printf '"cpu":{"count":%d,"model":"%s","frequency":%s,"temperature":%s,' \\ - "$cpu_count" "$cpu_model" "$cpu_freq" "$cpu_temp" - - printf '"total":' - awk 'NR==1 { - printf "["; - for(i=2; i<=NF; i++) { - if(i>2) printf ","; - printf "%d", $i; - } - printf "]"; - exit - }' /proc/stat - - printf ',"cores":[' - cpu_cores=$(nproc) - awk -v n="$cpu_cores" 'BEGIN{c=0} - /^cpu[0-9]+/ { - if(c>0) printf ","; - printf "[%d,%d,%d,%d,%d,%d,%d,%d]", $2,$3,$4,$5,$6,$7,$8,$9; - c++; - if(c==n) exit - }' /proc/stat - printf ']},' - - printf '"network":[' - tmp_net=$(mktemp) - grep -E '(wlan|eth|enp|wlp|ens|eno)' /proc/net/dev > "$tmp_net" || true - nfirst=1 - while IFS= read -r line; do - [ -z "$line" ] && continue - iface=$(echo "$line" | awk '{print $1}' | sed 's/://') - rx_bytes=$(echo "$line" | awk '{print $2}') - tx_bytes=$(echo "$line" | awk '{print $10}') - [ $nfirst -eq 1 ] || printf "," - printf '{"name":"%s","rx":%d,"tx":%d}' "$iface" "$rx_bytes" "$tx_bytes" - nfirst=0 - done < "$tmp_net" - rm -f "$tmp_net" - printf '],' - - printf '"disk":[' - tmp_disk=$(mktemp) - grep -E ' (sd[a-z]+|nvme[0-9]+n[0-9]+|vd[a-z]+|dm-[0-9]+|mmcblk[0-9]+) ' /proc/diskstats > "$tmp_disk" || true - dfirst=1 - while IFS= read -r line; do - [ -z "$line" ] && continue - name=$(echo "$line" | awk '{print $3}') - read_sectors=$(echo "$line" | awk '{print $6}') - write_sectors=$(echo "$line" | awk '{print $10}') - [ $dfirst -eq 1 ] || printf "," - printf '{"name":"%s","read":%d,"write":%d}' "$name" "$read_sectors" "$write_sectors" - dfirst=0 - done < "$tmp_disk" - rm -f "$tmp_disk" - printf '],' - - printf '"processes":[' - case "$sort_key" in - cpu) SORT_OPT="--sort=-pcpu" ;; - memory) SORT_OPT="--sort=-pmem" ;; - name) SORT_OPT="--sort=+comm" ;; - pid) SORT_OPT="--sort=+pid" ;; - *) SORT_OPT="--sort=-pcpu" ;; - esac - - tmp_ps=$(mktemp) - ps -eo pid,ppid,pcpu,pmem,rss,comm,cmd --no-headers $SORT_OPT | head -n "$max_procs" > "$tmp_ps" || true - - pfirst=1 - while IFS=' ' read -r pid ppid cpu pmem_rss rss_kib comm rest; do - [ -z "$pid" ] && continue - - # Per-process CPU ticks (utime+stime) - pticks=$(awk '{print $14+$15}' "/proc/$pid/stat" 2>/dev/null || echo 0) - - if [ "\${rss_kib:-0}" -eq 0 ]; then pss_kib=0; else pss_kib=$(get_pss_kb "$pid"); fi - case "$pss_kib" in (''|*[!0-9]*) pss_kib=0 ;; esac - pss_pct=$(LC_ALL=C awk -v p="$pss_kib" -v t="$MT" 'BEGIN{if(t>0) printf "%.2f", (100*p)/t; else printf "0.00"}') - - cmd=$(printf "%s %s" "$comm" "\${rest:-}" | json_escape) - comm_esc=$(printf "%s" "$comm" | json_escape) - - [ $pfirst -eq 1 ] || printf "," - printf '{"pid":%s,"ppid":%s,"cpu":%s,"pticks":%s,"memoryPercent":%s,"memoryKB":%s,"pssKB":%s,"pssPercent":%s,"command":"%s","fullCommand":"%s"}' \ - "$pid" "$ppid" "$cpu" "$pticks" "$pss_pct" "$rss_kib" "$pss_kib" "$pss_pct" "$comm_esc" "$cmd" - pfirst=0 - done < "$tmp_ps" - rm -f "$tmp_ps" - printf '],' - - load_avg=$(cut -d' ' -f1-3 /proc/loadavg) - proc_count=$(ls -Ud /proc/[0-9]* 2>/dev/null | wc -l) - thread_count=$(ls -Ud /proc/[0-9]*/task/[0-9]* 2>/dev/null | wc -l) - boot_time=$(who -b 2>/dev/null | awk '{print $3, $4}' | json_escape || echo 'Unknown') - - printf '"system":{"loadavg":"%s","processes":%d,"threads":%d,"boottime":"%s"},' \\ - "$load_avg" "$proc_count" "$thread_count" "$boot_time" - - printf '"diskmounts":[' - tmp_mounts=$(mktemp) - df -h --output=source,target,fstype,size,used,avail,pcent | tail -n +2 | grep -vE '^(tmpfs|devtmpfs)' | head -n 10 > "$tmp_mounts" || true - mfirst=1 - while IFS= read -r line; do - [ -z "$line" ] && continue - device=$(echo "$line" | awk '{print $1}' | json_escape) - mount=$(echo "$line" | awk '{print $2}' | json_escape) - fstype=$(echo "$line" | awk '{print $3}') - size=$(echo "$line" | awk '{print $4}') - used=$(echo "$line" | awk '{print $5}') - avail=$(echo "$line" | awk '{print $6}') - percent=$(echo "$line" | awk '{print $7}') - [ $mfirst -eq 1 ] || printf "," - printf '{"device":"%s","mount":"%s","fstype":"%s","size":"%s","used":"%s","avail":"%s","percent":"%s"}' \\ - "$device" "$mount" "$fstype" "$size" "$used" "$avail" "$percent" - mfirst=0 - done < "$tmp_mounts" - rm -f "$tmp_mounts" - printf ']', - - printf '"gputemps":[' - if [ "$collect_gpu_temps" = "1" ]; then - gfirst=1 - tmp_gpu=$(mktemp) - - # Gather GPU temperatures only - for card in /sys/class/drm/card*; do - [ -e "$card/device/driver" ] || continue - - drv=$(basename "$(readlink -f "$card/device/driver")") - drv=\${drv##*/} - - # Temperature (only scan hwmon for non-NVIDIA GPUs) - hw=""; temp="0" - if [ "$collect_non_nvidia" = "1" ]; then - for h in "$card/device"/hwmon/hwmon*; do - [ -e "$h/temp1_input" ] || continue - hw=$(basename "$h") - temp=$(awk '{printf "%.1f",$1/1000}' "$h/temp1_input" 2>/dev/null || echo "0") - break - done - fi - - # NVIDIA temperature fallback - if [ "$drv" = "nvidia" ] && [ "$temp" = "0" ] && [ "$collect_nvidia_only" = "1" ] && command -v nvidia-smi >/dev/null 2>&1; then - t=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) - [ -n "$t" ] && { temp="$t"; hw="\${hw:-nvidia}"; } - fi - - [ "$temp" != "0" ] && { - [ $gfirst -eq 1 ] || printf "," - printf '{"driver":"%s","hwmon":"%s","temperature":%s}' "$drv" "\${hw:-unknown}" "\${temp:-0}" - gfirst=0 - } - done - - # Fallback if no DRM cards found but nvidia-smi is available - if [ $gfirst -eq 1 ] && [ \"$collect_nvidia_only\" = \"1\" ]; then - if command -v nvidia-smi >/dev/null 2>&1; then - temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) - [ -n "$temp" ] && { - printf '{"driver":"nvidia","hwmon":"nvidia","temperature":%s}' "$temp" - gfirst=0 - } - fi - fi - - rm -f "$tmp_gpu" - fi - printf ']' - - printf "}\\n"` - Process { id: staticDataProcess - command: ["bash", "-c", "bash -s <<'QS_EOF'\n" - + root.staticDataScript + "\nQS_EOF\n"] + command: [root.shellDir + "/sysmon_static.sh"] running: false onExited: exitCode => { if (exitCode !== 0) { @@ -979,8 +636,7 @@ readonly property string staticDataScript: `exec 2>/dev/null Process { id: dynamicStatsProcess - command: ["bash", "-c", "bash -s \"$1\" \"$2\" \"$3\" \"$4\" \"$5\" <<'QS_EOF'\n" - + root.dynamicDataScript + "\nQS_EOF\n", "-", root.sortBy, String(root.maxProcesses), root.hasGpuTempWidgets() ? "1" : "0", root.hasNvidiaGpuTempWidgets() ? "1" : "0", root.hasNonNvidiaGpuTempWidgets() ? "1" : "0"] + command: [root.shellDir + "/sysmon_dynamic.sh", root.sortBy, String(root.maxProcesses), root.hasGpuTempWidgets() ? "1" : "0", root.hasNvidiaGpuTempWidgets() ? "1" : "0", root.hasNonNvidiaGpuTempWidgets() ? "1" : "0"] running: false onExited: exitCode => { if (exitCode !== 0) { diff --git a/sysmon_dynamic.sh b/sysmon_dynamic.sh new file mode 100755 index 00000000..f9bb2019 --- /dev/null +++ b/sysmon_dynamic.sh @@ -0,0 +1,254 @@ +#!/usr/bin/env bash +# Outputs dynamic system stats as JSON. +# Args: +# $1 sort_key (cpu|memory|name|pid) +# $2 max_procs +# $3 collect_gpu_temps (0/1) +# $4 collect_nvidia_only (0/1) +# $5 collect_non_nvidia (0/1) + +set -o pipefail + +sort_key=${1:-cpu} +max_procs=${2:-20} +collect_gpu_temps=${3:-0} +collect_nvidia_only=${4:-0} +collect_non_nvidia=${5:-1} + +json_escape() { sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e ':a;N;$!ba;s/\n/\\n/g'; } + +printf "{" + +# ---- Memory block (exact fields/keys) ---- +mem_line="$(awk ' + /^MemTotal:/ {t=$2} + /^MemFree:/ {f=$2} + /^MemAvailable:/ {a=$2} + /^Buffers:/ {b=$2} + /^Cached:/ {c=$2} + /^Shmem:/ {s=$2} + /^SwapTotal:/ {st=$2} + /^SwapFree:/ {sf=$2} + END{printf "%d %d %d %d %d %d %d %d", t,f,a,b,c,s,st,sf} +' /proc/meminfo)" +read -r MT MF MA BU CA SH ST SF <<< "$mem_line" + +printf '"memory":{"total":%d,"free":%d,"available":%d,"buffers":%d,"cached":%d,"shared":%d,"swaptotal":%d,"swapfree":%d},' \ + "$MT" "$MF" "$MA" "$BU" "$CA" "$SH" "$ST" "$SF" + +# ---- Helper: PSS in KiB ---- +get_pss_kb() { + local pid="$1" k v rest total=0 f + f="/proc/$pid/smaps_rollup" + if [ -r "$f" ]; then + while read -r k v rest; do + if [ "$k" = "Pss:" ]; then + printf '%s\n' "${v:-0}" + return + fi + done < "$f" + printf '0\n'; return + fi + f="/proc/$pid/smaps" + if [ -r "$f" ]; then + while read -r k v rest; do + if [ "$k" = "Pss:" ]; then + : "${v:=0}" + total=$(( total + v )) + fi + done < "$f" + printf '%s\n' "$total"; return + fi + printf '0\n' +} + +# ---- CPU (meta + temp) ---- +cpu_count=$(nproc) +cpu_model=$(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2- | sed 's/^ *//' | json_escape || echo 'Unknown') +cpu_freq=$(awk -F: '/cpu MHz/{gsub(/ /,"",$2);print $2;exit}' /proc/cpuinfo || echo 0) + +cpu_temp=0 +for hwmon_dir in /sys/class/hwmon/hwmon*/; do + [ -d "$hwmon_dir" ] || continue + name_file="${hwmon_dir}name" + [ -r "$name_file" ] || continue + if grep -qE 'coretemp|k10temp|k8temp|cpu_thermal|soc_thermal' "$name_file" 2>/dev/null; then + for temp_file in "${hwmon_dir}"temp*_input; do + [ -r "$temp_file" ] || continue + cpu_temp=$(awk '{printf "%.1f", $1/1000}' "$temp_file" 2>/dev/null || echo 0) + break 2 + done + fi +done + +printf '"cpu":{"count":%d,"model":"%s","frequency":%s,"temperature":%s,' \ + "$cpu_count" "$cpu_model" "$cpu_freq" "$cpu_temp" + +# cpu.total (first line of /proc/stat, fields 2..) +printf '"total":' +awk 'NR==1 { + printf "["; + for(i=2; i<=NF; i++) { if(i>2) printf ","; printf "%d", $i; } + printf "]"; + exit +}' /proc/stat + +# cpu.cores (each cpuN line, 8 columns) +printf ',"cores":[' +cpu_cores=$(nproc) +awk -v n="$cpu_cores" 'BEGIN{c=0} + /^cpu[0-9]+/ { + if(c>0) printf ","; + printf "[%d,%d,%d,%d,%d,%d,%d,%d]", $2,$3,$4,$5,$6,$7,$8,$9; + c++; + if(c==n) exit + }' /proc/stat +printf ']},' + +# ---- Network (rx/tx bytes per iface) ---- +printf '"network":[' +tmp_net=$(mktemp) +grep -E '(wlan|eth|enp|wlp|ens|eno)' /proc/net/dev > "$tmp_net" || true +nfirst=1 +while IFS= read -r line; do + [ -z "$line" ] && continue + iface=$(echo "$line" | awk '{print $1}' | sed 's/://') + rx_bytes=$(echo "$line" | awk '{print $2}') + tx_bytes=$(echo "$line" | awk '{print $10}') + [ $nfirst -eq 1 ] || printf "," + printf '{"name":"%s","rx":%d,"tx":%d}' "$iface" "$rx_bytes" "$tx_bytes" + nfirst=0 +done < "$tmp_net" +rm -f "$tmp_net" +printf '],' + +# ---- Disk (/proc/diskstats sectors; read/write) ---- +printf '"disk":[' +tmp_disk=$(mktemp) +grep -E ' (sd[a-z]+|nvme[0-9]+n[0-9]+|vd[a-z]+|dm-[0-9]+|mmcblk[0-9]+) ' /proc/diskstats > "$tmp_disk" || true +dfirst=1 +while IFS= read -r line; do + [ -z "$line" ] && continue + name=$(echo "$line" | awk '{print $3}') + read_sectors=$(echo "$line" | awk '{print $6}') + write_sectors=$(echo "$line" | awk '{print $10}') + [ $dfirst -eq 1 ] || printf "," + printf '{"name":"%s","read":%d,"write":%d}' "$name" "$read_sectors" "$write_sectors" + dfirst=0 +done < "$tmp_disk" +rm -f "$tmp_disk" +printf '],' + +# ---- Processes (shape & fields match your QML) ---- +case "$sort_key" in + cpu) SORT_OPT="--sort=-pcpu" ;; + memory) SORT_OPT="--sort=-pmem" ;; + name) SORT_OPT="--sort=+comm" ;; + pid) SORT_OPT="--sort=+pid" ;; + *) SORT_OPT="--sort=-pcpu" ;; +esac + +printf '"processes":[' +tmp_ps=$(mktemp) +ps -eo pid,ppid,pcpu,pmem,rss,comm,cmd --no-headers $SORT_OPT | head -n "$max_procs" > "$tmp_ps" || true +pfirst=1 +while IFS= read -r line; do + [ -z "$line" ] && continue + # split the first 6 columns, rest is full command tail + pid=$( awk '{print $1}' <<<"$line") + ppid=$( awk '{print $2}' <<<"$line") + cpu=$( awk '{print $3}' <<<"$line") + pmem=$( awk '{print $4}' <<<"$line") + rss_kib=$(awk '{print $5}' <<<"$line") + comm=$( awk '{print $6}' <<<"$line") + rest=$( printf '%s\n' "$line" | cut -d' ' -f7- ) + + # CPU ticks (utime+stime) + pticks=$(awk '{print $14+$15}' "/proc/$pid/stat" 2>/dev/null || echo 0) + + # PSS in KiB and % of MemTotal + if [ "${rss_kib:-0}" -eq 0 ]; then pss_kib=0; else pss_kib=$(get_pss_kb "$pid"); fi + case "$pss_kib" in (''|*[!0-9]*) pss_kib=0 ;; esac + pss_pct=$(LC_ALL=C awk -v p="$pss_kib" -v t="$MT" 'BEGIN{if(t>0) printf "%.2f", (100*p)/t; else printf "0.00"}') + + cmd=$(printf "%s %s" "$comm" "${rest:-}" | json_escape) + comm_esc=$(printf "%s" "$comm" | json_escape) + + [ $pfirst -eq 1 ] || printf "," + printf '{"pid":%s,"ppid":%s,"cpu":%s,"pticks":%s,"memoryPercent":%s,"memoryKB":%s,"pssKB":%s,"pssPercent":%s,"command":"%s","fullCommand":"%s"}' \ + "$pid" "$ppid" "$cpu" "$pticks" "$pss_pct" "$rss_kib" "$pss_kib" "$pss_pct" "$comm_esc" "$cmd" + pfirst=0 +done < "$tmp_ps" +rm -f "$tmp_ps" +printf '],' + +# ---- System (dynamic bits) ---- +load_avg=$(cut -d' ' -f1-3 /proc/loadavg) +proc_count=$(ls -Ud /proc/[0-9]* 2>/dev/null | wc -l) +thread_count=$(ls -Ud /proc/[0-9]*/task/[0-9]* 2>/dev/null | wc -l) +boot_time=$(who -b 2>/dev/null | awk '{print $3, $4}' | json_escape || echo 'Unknown') + +printf '"system":{"loadavg":"%s","processes":%d,"threads":%d,"boottime":"%s"},' \ + "$load_avg" "$proc_count" "$thread_count" "$boot_time" + +# ---- Mounts (same df -h shape/strings) ---- +printf '"diskmounts":[' +tmp_mounts=$(mktemp) +df -h --output=source,target,fstype,size,used,avail,pcent | tail -n +2 | grep -vE '^(tmpfs|devtmpfs)' > "$tmp_mounts" || true +mfirst=1 +while IFS= read -r line; do + [ -z "$line" ] && continue + device=$(echo "$line" | awk '{print $1}' | json_escape) + mount=$( echo "$line" | awk '{print $2}' | json_escape) + fstype=$(echo "$line" | awk '{print $3}') + size=$( echo "$line" | awk '{print $4}') + used=$( echo "$line" | awk '{print $5}') + avail=$( echo "$line" | awk '{print $6}') + percent=$(echo "$line" | awk '{print $7}') + [ $mfirst -eq 1 ] || printf "," + printf '{"device":"%s","mount":"%s","fstype":"%s","size":"%s","used":"%s","avail":"%s","percent":"%s"}' \ + "$device" "$mount" "$fstype" "$size" "$used" "$avail" "$percent" + mfirst=0 +done < "$tmp_mounts" +rm -f "$tmp_mounts" +printf '],' + +# ---- GPU temps (optional) ---- +printf '"gputemps":[' +if [ "$collect_gpu_temps" = "1" ]; then + gfirst=1 + for card in /sys/class/drm/card*; do + [ -e "$card/device/driver" ] || continue + drv=$(basename "$(readlink -f "$card/device/driver")"); drv=${drv##*/} + hw=""; temp="0" + + if [ "$collect_non_nvidia" = "1" ]; then + for h in "$card/device"/hwmon/hwmon*; do + [ -e "$h/temp1_input" ] || continue + hw=$(basename "$h") + temp=$(awk '{printf "%.1f",$1/1000}' "$h/temp1_input" 2>/dev/null || echo "0") + break + done + fi + + if [ "$drv" = "nvidia" ] && [ "$temp" = "0" ] && [ "$collect_nvidia_only" = "1" ] && command -v nvidia-smi >/dev/null 2>&1; then + t=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) + [ -n "$t" ] && { temp="$t"; hw="${hw:-nvidia}"; } + fi + + if [ "$temp" != "0" ]; then + [ $gfirst -eq 1 ] || printf "," + printf '{"driver":"%s","hwmon":"%s","temperature":%s}' "$drv" "${hw:-unknown}" "${temp:-0}" + gfirst=0 + fi + done + + # Fallback: nvidia-smi only + if [ ${gfirst:-1} -eq 1 ] && [ "$collect_nvidia_only" = "1" ] && command -v nvidia-smi >/dev/null 2>&1; then + temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1) + [ -n "$temp" ] && printf '{"driver":"nvidia","hwmon":"nvidia","temperature":%s}' "$temp" + fi +fi +printf ']' + +printf "}\n" diff --git a/sysmon_static.sh b/sysmon_static.sh new file mode 100755 index 00000000..18b295a1 --- /dev/null +++ b/sysmon_static.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Outputs static system info + detected GPUs as JSON (no temps) + +set -o pipefail +exec 2>/dev/null + +json_escape() { sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e ':a;N;$!ba;s/\n/\\n/g'; } + +printf "{" + +cpu_count=$(nproc) +cpu_model=$(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2- | sed 's/^ *//' | json_escape || echo 'Unknown') +printf '"cpu":{"count":%d,"model":"%s"},' "$cpu_count" "$cpu_model" + +dmip="/sys/class/dmi/id" +[ -d "$dmip" ] || dmip="/sys/devices/virtual/dmi/id" +mb_vendor=$([ -r "$dmip/board_vendor" ] && cat "$dmip/board_vendor" | json_escape || echo "Unknown") +mb_name=$([ -r "$dmip/board_name" ] && cat "$dmip/board_name" | json_escape || echo "") +bios_ver=$([ -r "$dmip/bios_version" ] && cat "$dmip/bios_version" | json_escape || echo "Unknown") +bios_date=$([ -r "$dmip/bios_date" ] && cat "$dmip/bios_date" | json_escape || echo "") + +kern_ver=$(uname -r | json_escape) +distro=$(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d= -f2- | tr -d '"' | json_escape || echo 'Unknown') +host_name=$(hostname | json_escape) +arch_name=$(uname -m) + +printf '"system":{"kernel":"%s","distro":"%s","hostname":"%s","arch":"%s","motherboard":"%s %s","bios":"%s %s"},' \ + "$kern_ver" "$distro" "$host_name" "$arch_name" "$mb_vendor" "$mb_name" "$bios_ver" "$bios_date" + +printf '"gpus":[' +gfirst=1 +tmp_gpu=$(mktemp) + +infer_vendor() { + case "$1" in + nvidia|nouveau) echo NVIDIA ;; + amdgpu|radeon) echo AMD ;; + i915|xe) echo Intel ;; + *) case "$2" in + *NVIDIA*|*Nvidia*|*nvidia*) echo NVIDIA ;; + *AMD*|*ATI*|*amd*|*ati*) echo AMD ;; + *Intel*|*intel*) echo Intel ;; + *) echo Unknown ;; + esac ;; + esac +} + +prio_of() { + local drv="$1" bdf="$2" + case "$drv" in + nvidia) echo 3 ;; + amdgpu|radeon) + local dd="${bdf##*:}"; dd="${dd%%.*}" + [ "$dd" = "00" ] && echo 1 || echo 2 + ;; + i915|xe) echo 0 ;; + *) echo 0 ;; + esac +} + +LC_ALL=C lspci -nnD 2>/dev/null | grep -iE ' VGA| 3D| 2D| Display' | while IFS= read -r line; do + bdf="${line%% *}" + drv="" + [ -e "/sys/bus/pci/devices/$bdf/driver" ] && drv="$(basename "$(readlink -f "/sys/bus/pci/devices/$bdf/driver")")" + vendor="$(infer_vendor "$drv" "$line")" + raw_line="$(printf '%s' "$line" | json_escape)" + prio="$(prio_of "$drv" "$bdf")" + printf '%s|%s|%s|%s\n' "$prio" "$drv" "$vendor" "$raw_line" >> "$tmp_gpu" +done + +if [ -s "$tmp_gpu" ]; then + while IFS='|' read -r pr drv vendor raw_line; do + [ $gfirst -eq 1 ] || printf "," + printf '{"driver":"%s","vendor":"%s","rawLine":"%s"}' "$drv" "$vendor" "$raw_line" + gfirst=0 + done < <(sort -t'|' -k1,1nr -k2,2 "$tmp_gpu") +fi + +rm -f "$tmp_gpu" +printf ']' +printf "}\n"