Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2025-12-14 - [Bash Script UX]
**Learning:** CLI tools in this repo are heavily macOS-dependent but lack compatibility checks, leading to confusing errors on other systems.
**Action:** When working on shell scripts, always add `command -v` checks or OS detection to provide clear feedback instead of raw errors.
137 changes: 95 additions & 42 deletions ops/monitoring/swap-ssd-health.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,93 +11,146 @@
# - BEFORE: System lag, high swap activity, potential data loss
# - AFTER: Proactive monitoring, early warning system, optimal performance

LOG_FILE="$HOME/Library/Logs/swap-ssd-health.log"
# ANSI Color Codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
RESET='\033[0m'

LOG_DIR="$HOME/Library/Logs"
LOG_FILE="$LOG_DIR/swap-ssd-health.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')

echo "=== Swap & SSD Health Check - $DATE ===" | tee -a "$LOG_FILE"
# Ensure log directory exists
if [ ! -d "$LOG_DIR" ]; then
mkdir -p "$LOG_DIR" 2>/dev/null || {
# Fallback to current directory if can't create in Library
LOG_DIR="."
LOG_FILE="./swap-ssd-health.log"
}
fi

# Helper function for logging
# Usage: log "Message" [COLOR]
log() {
local message="$1"
local color="${2:-$RESET}"

# Print to console with color
echo -e "${color}${message}${RESET}"

# Strip color codes for log file
echo "$message" | sed 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE"
}

log "=== Swap & SSD Health Check - $DATE ===" "$BOLD$BLUE"

# Check Swap Usage
echo "--- Swap Usage ---" | tee -a "$LOG_FILE"
SWAP_INFO=$(sysctl vm.swapusage 2>/dev/null)
echo "$SWAP_INFO" | tee -a "$LOG_FILE"

# Extract swap usage values
TOTAL_SWAP=$(echo "$SWAP_INFO" | grep 'total' | awk '{print $4}' | sed 's/M//' | sed 's/\..*//')
USED_SWAP=$(echo "$SWAP_INFO" | grep 'used' | awk '{print $4}' | sed 's/M//' | sed 's/\..*//')

# Check if swap usage is high (>50% of total)
if [ ! -z "$TOTAL_SWAP" ] && [ ! -z "$USED_SWAP" ]; then
SWAP_PERCENT=$((USED_SWAP * 100 / TOTAL_SWAP))
if [ $SWAP_PERCENT -gt 50 ]; then
echo "⚠️ WARNING: High swap usage detected ($SWAP_PERCENT%)" | tee -a "$LOG_FILE"
echo " Consider closing unused apps or adding more RAM" | tee -a "$LOG_FILE"
log "--- Swap Usage ---" "$BOLD"

if command -v sysctl &> /dev/null; then
SWAP_INFO=$(sysctl vm.swapusage 2>/dev/null)
log "$SWAP_INFO"

# Extract swap usage values
TOTAL_SWAP=$(echo "$SWAP_INFO" | grep 'total' | awk '{print $4}' | sed 's/M//' | sed 's/\..*//')
USED_SWAP=$(echo "$SWAP_INFO" | grep 'used' | awk '{print $4}' | sed 's/M//' | sed 's/\..*//')

# Check if swap usage is high (>50% of total)
if [ ! -z "$TOTAL_SWAP" ] && [ ! -z "$USED_SWAP" ] && [ "$TOTAL_SWAP" -gt 0 ]; then
SWAP_PERCENT=$((USED_SWAP * 100 / TOTAL_SWAP))
if [ $SWAP_PERCENT -gt 50 ]; then
log "⚠️ WARNING: High swap usage detected ($SWAP_PERCENT%)" "$YELLOW"
log " Consider closing unused apps or adding more RAM"
else
log "✅ Swap usage is normal ($SWAP_PERCENT%)" "$GREEN"
fi
else
echo "✅ Swap usage is normal ($SWAP_PERCENT%)" | tee -a "$LOG_FILE"
log "ℹ️ Swap usage info not parsable or swap is 0"
fi
else
log "ℹ️ sysctl not available (not on macOS?)" "$YELLOW"
fi

# Check Pageouts (indicates active swapping)
echo "--- Pageouts (Active Swapping) ---" | tee -a "$LOG_FILE"
PAGEOUTS=$(vm_stat | grep 'pageouts' | awk '{print $2}' | sed 's/\.//')
if [ ! -z "$PAGEOUTS" ] && [ $PAGEOUTS -gt 0 ]; then
echo "⚠️ WARNING: Active swapping detected ($PAGEOUTS pageouts)" | tee -a "$LOG_FILE"
echo " System is using disk as virtual memory" | tee -a "$LOG_FILE"
log "--- Pageouts (Active Swapping) ---" "$BOLD"

if command -v vm_stat &> /dev/null; then
PAGEOUTS=$(vm_stat | grep 'pageouts' | awk '{print $2}' | sed 's/\.//')
if [ ! -z "$PAGEOUTS" ] && [ $PAGEOUTS -gt 0 ]; then
log "⚠️ WARNING: Active swapping detected ($PAGEOUTS pageouts)" "$YELLOW"
log " System is using disk as virtual memory"
else
log "✅ No active swapping detected" "$GREEN"
fi
else
echo "✅ No active swapping detected" | tee -a "$LOG_FILE"
log "ℹ️ vm_stat not available" "$YELLOW"
fi

# Check SSD SMART Status
echo "--- SSD Health ---" | tee -a "$LOG_FILE"
log "--- SSD Health ---" "$BOLD"

# Basic SMART status
SMART_STATUS=$(diskutil info disk0 | grep 'SMART Status' | awk '{print $3}')
echo "SMART Status: $SMART_STATUS" | tee -a "$LOG_FILE"

if [ "$SMART_STATUS" != "Verified" ]; then
echo "⚠️ WARNING: SSD SMART status is not verified" | tee -a "$LOG_FILE"
if command -v diskutil &> /dev/null; then
SMART_STATUS=$(diskutil info disk0 | grep 'SMART Status' | awk '{print $3}')
if [ ! -z "$SMART_STATUS" ]; then
log "SMART Status: $SMART_STATUS"

if [ "$SMART_STATUS" != "Verified" ]; then
log "⚠️ WARNING: SSD SMART status is not verified" "$YELLOW"
else
log "✅ SSD SMART status is verified" "$GREEN"
fi
else
log "ℹ️ Could not retrieve SMART status via diskutil" "$YELLOW"
fi
else
echo "✅ SSD SMART status is verified" | tee -a "$LOG_FILE"
log "ℹ️ diskutil not available" "$YELLOW"
fi

# Detailed SSD health (if smartctl is available)
if command -v smartctl &> /dev/null; then
echo "--- Detailed SSD Health ---" | tee -a "$LOG_FILE"
log "--- Detailed SSD Health ---" "$BOLD"

# Get SSD wear indicators
SMART_OUTPUT=$(sudo smartctl -a disk0 2>/dev/null)

# Check for wear leveling count
WEAR_COUNT=$(echo "$SMART_OUTPUT" | grep 'Wear_Leveling_Count' | awk '{print $10}')
if [ ! -z "$WEAR_COUNT" ]; then
echo "Wear Leveling Count: $WEAR_COUNT" | tee -a "$LOG_FILE"
log "Wear Leveling Count: $WEAR_COUNT"
if [ $WEAR_COUNT -lt 10 ]; then
echo "⚠️ WARNING: SSD wear leveling count is low" | tee -a "$LOG_FILE"
log "⚠️ WARNING: SSD wear leveling count is low" "$YELLOW"
fi
fi

# Check for reallocated sectors
REALLOCATED=$(echo "$SMART_OUTPUT" | grep 'Reallocated_Sector_Ct' | awk '{print $10}')
if [ ! -z "$REALLOCATED" ] && [ $REALLOCATED -gt 0 ]; then
echo "⚠️ WARNING: Reallocated sectors detected ($REALLOCATED)" | tee -a "$LOG_FILE"
log "⚠️ WARNING: Reallocated sectors detected ($REALLOCATED)" "$YELLOW"
fi

# Check media wearout indicator
MEDIA_WEAR=$(echo "$SMART_OUTPUT" | grep 'Media_Wearout_Indicator' | awk '{print $10}')
if [ ! -z "$MEDIA_WEAR" ]; then
echo "Media Wearout Indicator: $MEDIA_WEAR" | tee -a "$LOG_FILE"
log "Media Wearout Indicator: $MEDIA_WEAR"
if [ $MEDIA_WEAR -lt 10 ]; then
echo "⚠️ WARNING: SSD media wearout indicator is low" | tee -a "$LOG_FILE"
log "⚠️ WARNING: SSD media wearout indicator is low" "$YELLOW"
fi
fi
else
echo "smartctl not available - install with: brew install smartmontools" | tee -a "$LOG_FILE"
log "ℹ️ smartctl not available - install with: brew install smartmontools" "$YELLOW"
fi

echo "--- End of Health Check ---" | tee -a "$LOG_FILE"
echo "" | tee -a "$LOG_FILE"
log "--- End of Health Check ---" "$BOLD$BLUE"
log ""

# Optional: Send notification if warnings were found
if grep -q "WARNING" "$LOG_FILE"; then
# Send macOS notification
osascript -e 'display notification "System health issues detected. Check logs for details." with title "Swap & SSD Health Check"'
if [ -f "$LOG_FILE" ] && grep -q "WARNING" "$LOG_FILE"; then
if command -v osascript &> /dev/null; then
# Send macOS notification
osascript -e 'display notification "System health issues detected. Check logs for details." with title "Swap & SSD Health Check"'
fi
fi
Loading