Skip to content

Instantly share code, notes, and snippets.

@denisfl
Last active March 30, 2026 21:04
Show Gist options
  • Select an option

  • Save denisfl/3001a0045f6f132b07125cd543f43168 to your computer and use it in GitHub Desktop.

Select an option

Save denisfl/3001a0045f6f132b07125cd543f43168 to your computer and use it in GitHub Desktop.
Simple monitoring script for RPI
#!/usr/bin/env bash
# ╔══════════════════════════════════════════════╗
# ║ Tokyo Night Bento Monitor ║
# ║ macOS · Linux · Raspberry Pi ║
# ╚══════════════════════════════════════════════╝
# ── Tokyo Night palette ──────────────────────────────────────
RESET=$'\e[0m'; BOLD=$'\e[1m'
C_BDR=$'\e[38;2;86;95;137m'
C_TTL=$'\e[38;2;122;162;247m'
C_TXT=$'\e[38;2;192;202;245m'
C_DIM=$'\e[38;2;86;95;137m'
C_GRN=$'\e[38;2;158;206;106m'
C_YLW=$'\e[38;2;224;175;104m'
C_RED=$'\e[38;2;247;118;142m'
C_CYN=$'\e[38;2;125;207;255m'
C_PRP=$'\e[38;2;187;154;247m'
C_ORG=$'\e[38;2;255;158;100m'
C_TEL=$'\e[38;2;42;195;222m'
# ── OS detection ─────────────────────────────────────────────
OS="linux"
[[ "$(uname)" == "Darwin" ]] && OS="macos"
IS_RPI=0
[[ -f /proc/device-tree/model ]] && grep -qi "raspberry" /proc/device-tree/model 2>/dev/null && IS_RPI=1
# ── Card drawing helpers ─────────────────────────────────────
hline() { printf '─%.0s' $(seq 1 "$1"); }
ctop() { printf "${C_BDR}╭$(hline $1)╮${RESET}\n"; }
cbot() { printf "${C_BDR}╰$(hline $1)╯${RESET}\n"; }
csep() { printf "${C_BDR}├$(hline $1)┤${RESET}\n"; }
cemp() { printf "${C_BDR}│%${1}s│${RESET}\n" ""; }
ctitle() {
local iw=$1 t=$2 tl=${#2}
local l=$(( (iw - tl) / 2 )) r=$(( iw - tl - (iw - tl) / 2 ))
printf "${C_BDR}│${RESET}%${l}s${BOLD}${C_TTL}%s${RESET}%${r}s${C_BDR}│${RESET}\n" "" "$t" ""
}
# cline <inner_w> <label> <value> [lc] [vc]
cline() {
local iw=$1 lbl=$2 val=$3 lc="${4:-$C_DIM}" vc="${5:-$C_TXT}"
local gap=$(( iw - 2 - ${#lbl} - ${#val} ))
[[ $gap -lt 1 ]] && gap=1
printf "${C_BDR}│${RESET} ${lc}%s${RESET}%${gap}s${vc}%s${RESET} ${C_BDR}│${RESET}\n" "$lbl" "" "$val"
}
# cbar <inner_w> <label> <bar_str> <bar_vis_w> <pct>
cbar() {
local iw=$1 lbl=$2 bstr=$3 bvw=$4 pct=$5
local pcts; printf -v pcts "%3d%%" "$pct"
local gap=$(( iw - 2 - ${#lbl} - bvw - 1 - ${#pcts} ))
[[ $gap -lt 1 ]] && gap=1
printf "${C_BDR}│${RESET} ${C_DIM}%s${RESET}%${gap}s%s ${C_TXT}%s${RESET} ${C_BDR}│${RESET}\n" \
"$lbl" "" "$bstr" "$pcts"
}
bar() {
local pct=$1 w=${2:-20}
local filled=$(( pct * w / 100 )) empty=$(( w - pct * w / 100 ))
local col="$C_GRN"
[[ $pct -ge 60 ]] && col="$C_YLW"
[[ $pct -ge 85 ]] && col="$C_RED"
local s="$col"
for ((i=0;i<filled;i++)); do s+="█"; done
s+="$C_DIM"
for ((i=0;i<empty;i++)); do s+="░"; done
printf "%s%s" "$s" "$RESET"
}
# sbs <iw_l> <iw_r> <left_arr_name> <right_arr_name>
sbs() {
local iwl=$1 iwr=$2
local -n _L=$3 _R=$4
local el er
printf -v el "${C_BDR}│%${iwl}s│${RESET}" ""
printf -v er "${C_BDR}│%${iwr}s│${RESET}" ""
local max=${#_L[@]}; [[ ${#_R[@]} -gt $max ]] && max=${#_R[@]}
for ((i=0;i<max;i++)); do
printf "%s %s\n" "${_L[$i]:-$el}" "${_R[$i]:-$er}"
done
}
# ── Data collectors ──────────────────────────────────────────
collect_common() {
HOSTNAME=$(hostname)
NOW=$(date '+%Y-%m-%d %H:%M:%S')
if [[ $OS == "macos" ]]; then
UPTIME=$(uptime | sed 's/.*up //' | sed 's/,.*//' | xargs)
else
UPTIME=$(uptime -p 2>/dev/null | sed 's/up //' || uptime | sed 's/.*up //' | sed 's/,.*//' | xargs)
fi
}
collect_cpu_macos() {
CPU_CORES=$(sysctl -n hw.logicalcpu 2>/dev/null || echo "?")
CPU_MODEL=$(sysctl -n machdep.cpu.brand_string 2>/dev/null \
| sed 's/Apple //' | sed 's/(R)//g' | sed 's/(TM)//g' \
| awk '{print substr($0,1,22)}')
# CPU usage via top (2 samples)
local idle
idle=$(top -l 2 -n 0 -s 1 2>/dev/null | grep "CPU usage" | tail -1 \
| grep -oP '[0-9.]+(?=% idle)')
[[ -n "$idle" ]] && CPU_PCT=$(awk "BEGIN{printf \"%d\",100-$idle}") || CPU_PCT=0
# CPU temp via powermetrics (needs sudo) or osx-cpu-temp if installed
if command -v osx-cpu-temp &>/dev/null; then
TEMP_RAW=$(osx-cpu-temp 2>/dev/null | grep -oP '[0-9.]+')
TEMP_UNIT="°C"
elif [[ -f /usr/bin/powermetrics ]]; then
# Try without sudo — returns nothing if no perms, graceful fallback
local pm
pm=$(sudo -n powermetrics -n 1 --samplers smc 2>/dev/null | grep "CPU die" | grep -oP '[0-9.]+' | head -1)
TEMP_RAW="${pm:-N/A}"; TEMP_UNIT="°C"
else
TEMP_RAW="N/A"; TEMP_UNIT=""
fi
ARM_FREQ="N/A"; GPU_FREQ="N/A"; THR_V="N/A"; THR_C="$C_DIM"
}
collect_cpu_linux() {
CPU_CORES=$(nproc 2>/dev/null || echo "?")
CPU_MODEL=$(grep -m1 "model name" /proc/cpuinfo 2>/dev/null \
| sed 's/.*: //' | sed 's/ */ /g' | awk '{print substr($0,1,22)}' \
|| echo "N/A")
local idle
idle=$(top -bn2 -d0.3 | grep "Cpu(s)" | tail -1 \
| awk '{for(i=1;i<=NF;i++) if($i~/id,/){gsub(/[^0-9.]/,"",$i);print $i}}')
[[ -n "$idle" ]] && CPU_PCT=$(awk "BEGIN{printf \"%d\",100-$idle}") || CPU_PCT=0
if [[ $IS_RPI -eq 1 ]] && command -v vcgencmd &>/dev/null; then
TEMP_RAW=$(vcgencmd measure_temp 2>/dev/null | grep -oP '[0-9.]+')
local ah gh
ah=$(vcgencmd measure_clock arm 2>/dev/null | grep -oP '\d+')
gh=$(vcgencmd measure_clock core 2>/dev/null | grep -oP '\d+')
[[ "$ah" =~ ^[0-9]+$ ]] && ARM_FREQ="$(( ah/1000000 )) MHz" || ARM_FREQ="N/A"
[[ "$gh" =~ ^[0-9]+$ ]] && GPU_FREQ="$(( gh/1000000 )) MHz" || GPU_FREQ="N/A"
local traw; traw=$(vcgencmd get_throttled 2>/dev/null | grep -oP '0x[0-9a-fA-F]+')
if [[ "$traw" == "0x0" ]]; then THR_V="none"; THR_C="$C_GRN"
elif [[ -n "$traw" ]]; then THR_V="$traw"; THR_C="$C_RED"
else THR_V="N/A"; THR_C="$C_DIM"; fi
else
TEMP_RAW=$(awk '{printf "%.1f",$1/1000}' /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo "N/A")
ARM_FREQ="N/A"; GPU_FREQ="N/A"; THR_V="N/A"; THR_C="$C_DIM"
fi
TEMP_UNIT="°C"
}
collect_mem_macos() {
# Physical RAM
local total_b; total_b=$(sysctl -n hw.memsize 2>/dev/null)
# vm_stat gives pages; pagesize from sysctl
local pagesize; pagesize=$(sysctl -n hw.pagesize 2>/dev/null || echo 4096)
local vm; vm=$(vm_stat 2>/dev/null)
local free_p wired_p active_p inactive_p specul_p compress_p
free_p=$( awk '/^Pages free:/ {gsub(/\./,"",$3);print $3}' <<< "$vm")
wired_p=$( awk '/^Pages wired down:/ {gsub(/\./,"",$4);print $4}' <<< "$vm")
active_p=$( awk '/^Pages active:/ {gsub(/\./,"",$3);print $3}' <<< "$vm")
compress_p=$(awk '/^Pages occupied by compressor:/ {gsub(/\./,"",$5);print $5}' <<< "$vm")
specul_p=$( awk '/^Pages speculative:/ {gsub(/\./,"",$3);print $3}' <<< "$vm")
# "used" = wired + active + compressed (excludes inactive/speculative)
local used_p=$(( (wired_p + active_p + compress_p) ))
local free_p_adj=$(( free_p + specul_p ))
local used_b=$(( used_p * pagesize ))
local free_b=$(( free_p_adj * pagesize ))
RAM_PCT=$(awk "BEGIN{printf \"%d\",($used_b/$total_b)*100}")
RAM_TOT=$(awk "BEGIN{printf \"%.1f GiB\",$total_b/1073741824}")
RAM_USE=$(awk "BEGIN{printf \"%.1f GiB\",$used_b/1073741824}")
RAM_FRE=$(awk "BEGIN{printf \"%.1f GiB\",$free_b/1073741824}")
# Swap
local swapinfo; swapinfo=$(sysctl -n vm.swapusage 2>/dev/null)
local stot suse
stot=$(awk '{for(i=1;i<=NF;i++) if($i=="total") {gsub(/M/,"",$i+1);print $i+1}}' <<< "$swapinfo")
suse=$(awk '{for(i=1;i<=NF;i++) if($i=="used") {gsub(/M/,"",$i+1);print $i+1}}' <<< "$swapinfo")
if [[ -n "$stot" && "$stot" != "0.00" ]]; then
SWP_PCT=$(awk "BEGIN{printf \"%d\",($suse/$stot)*100}" 2>/dev/null || echo 0)
SWP_USE=$(awk "BEGIN{printf \"%.0f MiB\",$suse}" 2>/dev/null || echo "0 MiB")
SWP_TOT=$(awk "BEGIN{printf \"%.0f MiB\",$stot}" 2>/dev/null || echo "0 MiB")
HAS_SWAP=1
else
SWP_PCT=0; SWP_USE="0"; SWP_TOT="0"; HAS_SWAP=0
fi
}
collect_mem_linux() {
read -r RB_TOT RB_USE RB_FRE <<< "$(free -b | awk '/^Mem:/{print $2,$3,$4}')"
RAM_PCT=$(awk "BEGIN{printf \"%d\",($RB_USE/$RB_TOT)*100}")
RAM_TOT=$(awk "BEGIN{printf \"%.1f GiB\",$RB_TOT/1073741824}")
RAM_USE=$(awk "BEGIN{printf \"%.1f GiB\",$RB_USE/1073741824}")
RAM_FRE=$(awk "BEGIN{printf \"%.1f GiB\",$RB_FRE/1073741824}")
read -r SB_TOT SB_USE <<< "$(free -b | awk '/^Swap:/{print $2,$3}')"
if [[ "${SB_TOT:-0}" -gt 0 ]]; then
SWP_PCT=$(awk "BEGIN{printf \"%d\",($SB_USE/$SB_TOT)*100}")
SWP_USE=$(awk "BEGIN{printf \"%.0f MiB\",$SB_USE/1048576}")
SWP_TOT=$(awk "BEGIN{printf \"%.0f MiB\",$SB_TOT/1048576}")
HAS_SWAP=1
else
SWP_PCT=0; SWP_USE="0"; SWP_TOT="0"; HAS_SWAP=0
fi
}
collect_disk() {
read -r DB_TOT DB_USE DB_FRE DISK_PCT <<< \
"$(df -B1 / | awk 'NR==2{gsub(/%/,"",$5);print $2,$3,$4,$5}' 2>/dev/null)"
# macOS df doesn't support -B1 — fallback
if [[ -z "$DB_TOT" ]] || [[ $OS == "macos" ]]; then
read -r DB_TOT DB_USE DB_FRE DISK_PCT <<< \
"$(df / | awk 'NR==2{gsub(/%/,"",$5);print $2*512,$3*512,$4*512,$5}' 2>/dev/null)"
fi
DISK_PCT=${DISK_PCT:-0}
DISK_TOT=$(awk "BEGIN{printf \"%.1f GiB\",$DB_TOT/1073741824}")
DISK_USE=$(awk "BEGIN{printf \"%.1f GiB\",$DB_USE/1073741824}")
DISK_FRE=$(awk "BEGIN{printf \"%.1f GiB\",$DB_FRE/1073741824}")
}
collect_net() {
if [[ $OS == "macos" ]]; then
NET_IF=$(route -n get default 2>/dev/null | awk '/interface:/{print $2}')
[[ -z "$NET_IF" ]] && NET_IF=$(netstat -rn 2>/dev/null | awk '/^default/{print $NF;exit}')
else
NET_IF=$(ip route 2>/dev/null | awk '/default/{print $5;exit}')
fi
if [[ -z "$NET_IF" ]]; then
NET_IF="N/A"; NET_IP="N/A"; RX="N/A"; TX="N/A"; return
fi
if [[ $OS == "macos" ]]; then
NET_IP=$(ipconfig getifaddr "$NET_IF" 2>/dev/null || echo "N/A")
# 1-second rx/tx delta via netstat
local s1 s2
s1=$(netstat -bI "$NET_IF" 2>/dev/null | awk 'NR==2{print $7,$10}')
sleep 1
s2=$(netstat -bI "$NET_IF" 2>/dev/null | awk 'NR==2{print $7,$10}')
local rx1 tx1 rx2 tx2
read -r rx1 tx1 <<< "$s1"; read -r rx2 tx2 <<< "$s2"
RX=$(awk "BEGIN{printf \"%.1f KB/s\",($rx2-$rx1)/1024}" 2>/dev/null || echo "N/A")
TX=$(awk "BEGIN{printf \"%.1f KB/s\",($tx2-$tx1)/1024}" 2>/dev/null || echo "N/A")
else
NET_IP=$(ip -4 addr show "$NET_IF" 2>/dev/null \
| awk '/inet /{gsub(/\/.*/,"",$2);print $2}')
local rx1 tx1 rx2 tx2
rx1=$(cat /sys/class/net/$NET_IF/statistics/rx_bytes 2>/dev/null)
tx1=$(cat /sys/class/net/$NET_IF/statistics/tx_bytes 2>/dev/null)
sleep 1
rx2=$(cat /sys/class/net/$NET_IF/statistics/rx_bytes 2>/dev/null)
tx2=$(cat /sys/class/net/$NET_IF/statistics/tx_bytes 2>/dev/null)
RX=$(awk "BEGIN{printf \"%.1f KB/s\",($rx2-$rx1)/1024}")
TX=$(awk "BEGIN{printf \"%.1f KB/s\",($tx2-$tx1)/1024}")
fi
}
collect_procs() {
if [[ $OS == "macos" ]]; then
mapfile -t TOP3 < <(
ps aux -r 2>/dev/null \
| awk 'NR>1&&NR<=4{print substr($11,1,15),$3,$4}'
)
else
mapfile -t TOP3 < <(
ps aux --sort=-%cpu 2>/dev/null \
| awk 'NR>1&&NR<=4{print substr($11,1,15),$3,$4}'
)
fi
}
collect() {
collect_common
if [[ $OS == "macos" ]]; then
collect_cpu_macos
collect_mem_macos
else
collect_cpu_linux
collect_mem_linux
fi
collect_disk
collect_net
collect_procs
# Temp color
local ti=${TEMP_RAW%.*}
if [[ "$ti" =~ ^[0-9]+$ && $ti -ge 80 ]]; then TEMP_C="$C_RED"
elif [[ "$ti" =~ ^[0-9]+$ && $ti -ge 65 ]]; then TEMP_C="$C_YLW"
elif [[ "$ti" =~ ^[0-9]+$ ]]; then TEMP_C="$C_GRN"
else TEMP_C="$C_DIM"; fi
}
# ── Render ────────────────────────────────────────────────────
render() {
clear
local FI=63 # full card inner width (total = 65)
local HI=30 # half card inner width (total = 32)
local BW=16 # bar visual width
printf "\n"
# ══ HEADER ══════════════════════════════════════════════════
ctop $FI
ctitle $FI "TOKYO NIGHT MONITOR"
csep $FI
if [[ $OS == "macos" ]]; then
cline $FI " host" "$HOSTNAME" "$C_DIM" "$C_CYN"
cline $FI " cpu" "$CPU_MODEL" "$C_DIM" "$C_TXT"
else
cline $FI " host" "$HOSTNAME" "$C_DIM" "$C_CYN"
fi
cline $FI " uptime" "$UPTIME" "$C_DIM" "$C_TXT"
cbot $FI
printf "\n"
# ══ ROW 1: THERMAL/CPU ══════════════════════════════════════
local -a CL CR
CL=()
CL+=("$(ctop $HI)")
if [[ $IS_RPI -eq 1 ]]; then
CL+=("$(ctitle $HI "THERMAL")")
CL+=("$(csep $HI)")
CL+=("$(cline $HI " temp" "${TEMP_RAW}${TEMP_UNIT}" "$C_DIM" "$TEMP_C")")
CL+=("$(cline $HI " arm" "$ARM_FREQ" "$C_DIM" "$C_ORG")")
CL+=("$(cline $HI " gpu" "$GPU_FREQ" "$C_DIM" "$C_PRP")")
CL+=("$(cline $HI " thrt" "$THR_V" "$C_DIM" "$THR_C")")
else
CL+=("$(ctitle $HI "THERMAL")")
CL+=("$(csep $HI)")
CL+=("$(cline $HI " temp" "${TEMP_RAW}${TEMP_UNIT}" "$C_DIM" "$TEMP_C")")
CL+=("$(cemp $HI)")
CL+=("$(cemp $HI)")
CL+=("$(cemp $HI)")
fi
CL+=("$(cbot $HI)")
CR=()
CR+=("$(ctop $HI)")
CR+=("$(ctitle $HI "CPU")")
CR+=("$(csep $HI)")
CR+=("$(cbar $HI " load" "$(bar $CPU_PCT $BW)" $BW $CPU_PCT)")
CR+=("$(cemp $HI)")
CR+=("$(cline $HI " cores" "$CPU_CORES" "$C_DIM" "$C_TXT")")
CR+=("$(cemp $HI)")
CR+=("$(cbot $HI)")
sbs $HI $HI CL CR
printf "\n"
# ══ ROW 2: MEMORY | DISK ════════════════════════════════════
CL=()
CL+=("$(ctop $HI)")
CL+=("$(ctitle $HI "MEMORY")")
CL+=("$(csep $HI)")
CL+=("$(cbar $HI " ram" "$(bar $RAM_PCT $BW)" $BW $RAM_PCT)")
CL+=("$(cline $HI " ├ used" "$RAM_USE" "$C_DIM" "$C_CYN")")
CL+=("$(cline $HI " └ free" "$RAM_FRE" "$C_DIM" "$C_GRN")")
if [[ $HAS_SWAP -eq 1 ]]; then
CL+=("$(csep $HI)")
CL+=("$(cbar $HI " swap" "$(bar $SWP_PCT $BW)" $BW $SWP_PCT)")
CL+=("$(cline $HI " └ used" "$SWP_USE / $SWP_TOT" "$C_DIM" "$C_YLW")")
fi
CL+=("$(cbot $HI)")
CR=()
CR+=("$(ctop $HI)")
CR+=("$(ctitle $HI "DISK /")")
CR+=("$(csep $HI)")
CR+=("$(cbar $HI " root" "$(bar $DISK_PCT $BW)" $BW $DISK_PCT)")
CR+=("$(cline $HI " ├ used" "$DISK_USE" "$C_DIM" "$C_CYN")")
CR+=("$(cline $HI " ├ free" "$DISK_FRE" "$C_DIM" "$C_GRN")")
CR+=("$(cline $HI " └ total" "$DISK_TOT" "$C_DIM" "$C_DIM")")
if [[ $HAS_SWAP -eq 1 ]]; then
CR+=("$(csep $HI)")
CR+=("$(cemp $HI)")
CR+=("$(cemp $HI)")
fi
CR+=("$(cbot $HI)")
sbs $HI $HI CL CR
printf "\n"
# ══ ROW 3: NETWORK | PROCESSES ══════════════════════════════
CL=()
CL+=("$(ctop $HI)")
CL+=("$(ctitle $HI "NETWORK")")
CL+=("$(csep $HI)")
CL+=("$(cline $HI " iface" "$NET_IF" "$C_DIM" "$C_TEL")")
CL+=("$(cline $HI " ip" "$NET_IP" "$C_DIM" "$C_CYN")")
CL+=("$(csep $HI)")
CL+=("$(cline $HI " ↓ rx" "$RX" "$C_DIM" "$C_GRN")")
CL+=("$(cline $HI " ↑ tx" "$TX" "$C_DIM" "$C_ORG")")
CL+=("$(cbot $HI)")
local _hdr
printf -v _hdr \
"${C_BDR}│${RESET} ${C_DIM}%-15s %5s %5s${RESET} ${C_BDR}│${RESET}" \
"command" "cpu%" "mem%"
CR=()
CR+=("$(ctop $HI)")
CR+=("$(ctitle $HI "PROCESSES")")
CR+=("$(csep $HI)")
CR+=("$_hdr")
for entry in "${TOP3[@]}"; do
local cmd cpu mem
read -r cmd cpu mem <<< "$entry"
local _row
printf -v _row \
"${C_BDR}│${RESET} ${C_CYN}%-15s${RESET} ${C_YLW}%5s${RESET} ${C_GRN}%5s${RESET} ${C_BDR}│${RESET}" \
"${cmd:0:15}" "${cpu:0:5}" "${mem:0:5}"
CR+=("$_row")
done
while [[ ${#CR[@]} -lt ${#CL[@]}-1 ]]; do CR+=("$(cemp $HI)"); done
CR+=("$(cbot $HI)")
sbs $HI $HI CL CR
# ── Footer ──────────────────────────────────────────────────
local os_tag="linux"
[[ $OS == "macos" ]] && os_tag="macos"
[[ $IS_RPI -eq 1 ]] && os_tag="rpi"
printf "\n ${C_DIM}%s · %s ${RESET}${C_YLW}ctrl+c${RESET}${C_DIM} to exit${RESET}\n\n" \
"$os_tag" "$NOW"
}
# ── Entry ─────────────────────────────────────────────────────
trap 'tput cnorm; printf "\n"; exit 0' INT TERM
tput civis
if [[ "$1" == "--watch" || "$1" == "-w" ]]; then
INTERVAL="${2:-5}"
while true; do collect; render; sleep "$INTERVAL"; done
else
collect; render; tput cnorm
fi
@denisfl

denisfl commented Mar 30, 2026 •

Copy link
Copy Markdown
Author

How to use

Install brew install osx-cpu-temp for mac

chmod +x rpi_monitor.sh 

./rpi_monitor.sh            # for once call
./rpi_monitor.sh -w       # update each 5 sec
./rpi_monitor.sh -w 10  # update each 10 sec
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment