monitor.sh

Download
bash 334 lines 8.5 KB
  1#!/usr/bin/env bash
  2set -euo pipefail
  3
  4# System Monitoring Dashboard
  5# Terminal-based real-time system monitoring with color-coded alerts
  6# Cross-platform support for Linux and macOS
  7
  8# ============================================================================
  9# Configuration
 10# ============================================================================
 11
 12REFRESH_INTERVAL=${REFRESH_INTERVAL:-2}  # seconds
 13
 14# Alert thresholds
 15CPU_WARNING=70
 16CPU_CRITICAL=90
 17MEM_WARNING=75
 18MEM_CRITICAL=90
 19DISK_WARNING=80
 20DISK_CRITICAL=95
 21
 22# Platform detection
 23OS_TYPE=$(uname -s)
 24
 25# Colors
 26RED='\033[0;31m'
 27GREEN='\033[0;32m'
 28YELLOW='\033[1;33m'
 29BLUE='\033[0;34m'
 30CYAN='\033[0;36m'
 31BOLD='\033[1m'
 32NC='\033[0m'
 33
 34# ============================================================================
 35# Terminal Control
 36# ============================================================================
 37
 38# Save cursor position and hide cursor
 39init_terminal() {
 40    tput smcup      # Save screen
 41    tput civis      # Hide cursor
 42    clear
 43}
 44
 45# Restore terminal on exit
 46cleanup_terminal() {
 47    tput rmcup      # Restore screen
 48    tput cnorm      # Show cursor
 49    clear
 50}
 51
 52# Position cursor at row, col
 53move_cursor() {
 54    local row=$1
 55    local col=$2
 56    tput cup "$row" "$col"
 57}
 58
 59# Get terminal dimensions
 60get_terminal_size() {
 61    TERM_ROWS=$(tput lines)
 62    TERM_COLS=$(tput cols)
 63}
 64
 65# ============================================================================
 66# Metric Collection Functions
 67# ============================================================================
 68
 69get_cpu_usage() {
 70    if [[ "$OS_TYPE" == "Linux" ]]; then
 71        # Linux: use top or /proc/stat
 72        if command -v top &> /dev/null; then
 73            top -bn2 -d 0.1 | grep '^%Cpu' | tail -n1 | awk '{print int(100 - $8)}'
 74        else
 75            echo "0"
 76        fi
 77    elif [[ "$OS_TYPE" == "Darwin" ]]; then
 78        # macOS: use top
 79        top -l 2 -n 0 -F | grep 'CPU usage' | tail -n1 | awk '{print int($3 + $5)}'
 80    else
 81        echo "0"
 82    fi
 83}
 84
 85get_memory_usage() {
 86    if [[ "$OS_TYPE" == "Linux" ]]; then
 87        # Linux: use /proc/meminfo
 88        if [[ -f /proc/meminfo ]]; then
 89            local total
 90            local available
 91            total=$(grep MemTotal /proc/meminfo | awk '{print $2}')
 92            available=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
 93            echo $(( (total - available) * 100 / total ))
 94        else
 95            echo "0"
 96        fi
 97    elif [[ "$OS_TYPE" == "Darwin" ]]; then
 98        # macOS: use vm_stat
 99        local page_size
100        local free_pages
101        local active_pages
102        local inactive_pages
103        local wired_pages
104
105        page_size=$(vm_stat | grep 'page size' | awk '{print $8}')
106        free_pages=$(vm_stat | grep 'Pages free' | awk '{print $3}' | tr -d '.')
107        active_pages=$(vm_stat | grep 'Pages active' | awk '{print $3}' | tr -d '.')
108        inactive_pages=$(vm_stat | grep 'Pages inactive' | awk '{print $3}' | tr -d '.')
109        wired_pages=$(vm_stat | grep 'Pages wired' | awk '{print $4}' | tr -d '.')
110
111        local used=$((active_pages + inactive_pages + wired_pages))
112        local total=$((used + free_pages))
113
114        echo $((used * 100 / total))
115    else
116        echo "0"
117    fi
118}
119
120get_disk_usage() {
121    # Get disk usage for root filesystem
122    df -h / | awk 'NR==2 {print int($5)}'
123}
124
125get_load_average() {
126    if [[ "$OS_TYPE" == "Linux" ]] || [[ "$OS_TYPE" == "Darwin" ]]; then
127        uptime | awk -F'load average:' '{print $2}' | awk '{print $1, $2, $3}' | tr -d ','
128    else
129        echo "0.00 0.00 0.00"
130    fi
131}
132
133get_top_processes() {
134    local count=${1:-5}
135
136    if [[ "$OS_TYPE" == "Linux" ]]; then
137        ps aux --sort=-%cpu | head -n $((count + 1)) | tail -n $count | awk '{printf "%-15s %5s%% %5s%%\n", substr($11,1,15), $3, $4}'
138    elif [[ "$OS_TYPE" == "Darwin" ]]; then
139        ps aux -r | head -n $((count + 1)) | tail -n $count | awk '{printf "%-15s %5s%% %5s%%\n", substr($11,1,15), $3, $4}'
140    fi
141}
142
143# ============================================================================
144# Display Functions
145# ============================================================================
146
147get_color() {
148    local value=$1
149    local warning=$2
150    local critical=$3
151
152    if [[ $value -ge $critical ]]; then
153        echo -e "$RED"
154    elif [[ $value -ge $warning ]]; then
155        echo -e "$YELLOW"
156    else
157        echo -e "$GREEN"
158    fi
159}
160
161draw_box() {
162    local row=$1
163    local col=$2
164    local width=$3
165    local height=$4
166    local title=$5
167
168    # Top border
169    move_cursor "$row" "$col"
170    echo -ne "${CYAN}┌"
171    printf '─%.0s' $(seq 1 $((width - 2)))
172    echo -ne "┐${NC}"
173
174    # Title
175    move_cursor "$row" $((col + 2))
176    echo -ne "${BOLD}${title}${NC}"
177
178    # Side borders
179    local i
180    for ((i = 1; i < height - 1; i++)); do
181        move_cursor $((row + i)) "$col"
182        echo -ne "${CYAN}${NC}"
183        move_cursor $((row + i)) $((col + width - 1))
184        echo -ne "${CYAN}${NC}"
185    done
186
187    # Bottom border
188    move_cursor $((row + height - 1)) "$col"
189    echo -ne "${CYAN}└"
190    printf '─%.0s' $(seq 1 $((width - 2)))
191    echo -ne "┘${NC}"
192}
193
194draw_bar() {
195    local value=$1
196    local max_width=$2
197    local warning=$3
198    local critical=$4
199
200    local filled=$((value * max_width / 100))
201    local color
202    color=$(get_color "$value" "$warning" "$critical")
203
204    echo -ne "$color"
205    printf '█%.0s' $(seq 1 "$filled")
206    echo -ne "$NC"
207    printf '░%.0s' $(seq 1 $((max_width - filled)))
208}
209
210draw_metric_panel() {
211    local row=$1
212    local col=$2
213    local width=$3
214    local title=$4
215    local value=$5
216    local warning=$6
217    local critical=$7
218
219    draw_box "$row" "$col" "$width" 5 "$title"
220
221    # Value
222    move_cursor $((row + 2)) $((col + 2))
223    local color
224    color=$(get_color "$value" "$warning" "$critical")
225    printf "%s%3d%%%s" "$color" "$value" "$NC"
226
227    # Progress bar
228    move_cursor $((row + 3)) $((col + 2))
229    draw_bar "$value" $((width - 4)) "$warning" "$critical"
230}
231
232draw_header() {
233    move_cursor 0 0
234    echo -ne "${BOLD}${CYAN}"
235    printf '═%.0s' $(seq 1 "$TERM_COLS")
236    echo -ne "$NC"
237
238    move_cursor 0 2
239    echo -ne "${BOLD}System Monitor${NC}"
240
241    move_cursor 0 $((TERM_COLS - 30))
242    echo -ne "${BOLD}$(date '+%Y-%m-%d %H:%M:%S')${NC}"
243
244    move_cursor 1 0
245    echo -ne "${BOLD}${CYAN}"
246    printf '═%.0s' $(seq 1 "$TERM_COLS")
247    echo -ne "$NC"
248}
249
250draw_dashboard() {
251    # Collect metrics
252    local cpu_usage
253    local mem_usage
254    local disk_usage
255    local load_avg
256
257    cpu_usage=$(get_cpu_usage)
258    mem_usage=$(get_memory_usage)
259    disk_usage=$(get_disk_usage)
260    load_avg=$(get_load_average)
261
262    # Clear and draw
263    clear
264    draw_header
265
266    # Calculate layout
267    local panel_width=$((TERM_COLS / 2 - 3))
268
269    # Top row - 4 metric panels
270    local panel_w=$((TERM_COLS / 4 - 2))
271    draw_metric_panel 3 2 "$panel_w" "CPU" "$cpu_usage" "$CPU_WARNING" "$CPU_CRITICAL"
272    draw_metric_panel 3 $((panel_w + 3)) "$panel_w" "Memory" "$mem_usage" "$MEM_WARNING" "$MEM_CRITICAL"
273    draw_metric_panel 3 $((panel_w * 2 + 4)) "$panel_w" "Disk" "$disk_usage" "$DISK_WARNING" "$DISK_CRITICAL"
274
275    # Load average
276    draw_box 3 $((panel_w * 3 + 5)) "$panel_w" 5 "Load Avg"
277    move_cursor 5 $((panel_w * 3 + 7))
278    echo -ne "$load_avg"
279
280    # Process list
281    local proc_row=9
282    draw_box "$proc_row" 2 $((TERM_COLS - 4)) 12 "Top Processes (CPU)"
283
284    move_cursor $((proc_row + 2)) 4
285    printf "${BOLD}%-15s %5s  %5s${NC}" "COMMAND" "CPU%" "MEM%"
286
287    local line=0
288    while IFS= read -r process; do
289        move_cursor $((proc_row + 3 + line)) 4
290        echo -ne "$process"
291        ((line++))
292    done < <(get_top_processes 8)
293
294    # Status line
295    move_cursor $((TERM_ROWS - 2)) 2
296    echo -ne "${BOLD}Press Ctrl+C to exit | Refresh: ${REFRESH_INTERVAL}s${NC}"
297}
298
299# ============================================================================
300# Main Loop
301# ============================================================================
302
303main() {
304    # Initialize
305    init_terminal
306    trap cleanup_terminal EXIT INT TERM
307
308    # Main monitoring loop
309    while true; do
310        get_terminal_size
311
312        # Ensure terminal is large enough
313        if [[ $TERM_ROWS -lt 24 ]] || [[ $TERM_COLS -lt 80 ]]; then
314            clear
315            move_cursor 2 2
316            echo -e "${RED}Terminal too small!${NC}"
317            move_cursor 3 2
318            echo "Minimum size: 80x24"
319            move_cursor 4 2
320            echo "Current size: ${TERM_COLS}x${TERM_ROWS}"
321            sleep 1
322            continue
323        fi
324
325        # Draw dashboard
326        draw_dashboard
327
328        # Wait for refresh interval
329        sleep "$REFRESH_INTERVAL"
330    done
331}
332
333main "$@"