Set up comprehensive process resource monitoring using cgroups v2 and systemd to track CPU, memory, and I/O usage with automated alerting when processes exceed defined limits.
Prerequisites
- Root or sudo access
- systemd-based Linux distribution
- Basic understanding of Linux processes
What this solves
This tutorial shows you how to implement production-grade process resource monitoring using cgroups v2 and systemd service units with defined resource limits. You'll learn to monitor CPU usage, memory consumption, and I/O throughput for critical applications, with automated alerts when processes exceed their allocated resources. This approach prevents resource exhaustion, improves system stability, and provides detailed insights into application performance patterns.
Step-by-step configuration
Verify cgroups v2 support
Check that your system is using cgroups v2, which provides unified hierarchy and improved resource management features.
mount | grep cgroup
cat /proc/filesystems | grep cgroup
You should see cgroup2 filesystem mounted at /sys/fs/cgroup. If you see cgroup v1, enable v2 by adding systemd.unified_cgroup_hierarchy=1 to your kernel boot parameters.
Install monitoring tools
Install the necessary packages for cgroup monitoring and system resource analysis.
sudo apt update
sudo apt install -y cgroup-tools systemd-cgroup-utils htop iotop sysstat mailutils
Create a sample application service
Create a test application that we'll monitor and apply resource limits to demonstrate the monitoring system.
sudo mkdir -p /opt/testapp
sudo tee /opt/testapp/cpu-intensive.sh > /dev/null << 'EOF'
#!/bin/bash
CPU intensive test application
echo "Starting CPU intensive application - PID: $$"
while true; do
# Simulate CPU work
dd if=/dev/zero of=/dev/null bs=1M count=100 2>/dev/null
sleep 2
done
EOF
sudo chmod 755 /opt/testapp/cpu-intensive.sh
Create systemd service with resource limits
Define a systemd service unit with specific CPU, memory, and I/O limits using cgroups v2 directives.
[Unit]
Description=Test Application for Resource Monitoring
After=network.target
[Service]
Type=simple
User=nobody
Group=nogroup
ExecStart=/opt/testapp/cpu-intensive.sh
Restart=always
RestartSec=10
CPU Limits - 50% of one CPU core
CPUQuota=50%
CPUWeight=100
Memory Limits - 512MB
MemoryMax=512M
MemoryHigh=400M
I/O Limits - 10MB/s read, 5MB/s write
IOReadBandwidthMax=/dev/sda 10M
IOWriteBandwidthMax=/dev/sda 5M
Task limits
TasksMax=50
Enable accounting
CPUAccounting=true
MemoryAccounting=true
IOAccounting=true
TasksAccounting=true
[Install]
WantedBy=multi-user.target
Create monitoring script
Build a comprehensive monitoring script that checks resource usage and sends alerts when limits are exceeded.
#!/bin/bash
Configuration
SERVICE_NAME="testapp"
ALERT_EMAIL="admin@example.com"
CPU_THRESHOLD=80 # Alert if CPU usage > 80% of quota
MEMORY_THRESHOLD=90 # Alert if memory usage > 90% of limit
LOG_FILE="/var/log/resource-monitor.log"
ALERT_COOLDOWN=300 # 5 minutes between identical alerts
COOLDOWN_FILE="/tmp/alert-cooldown-${SERVICE_NAME}"
Logging function
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
Check if service is running
if ! systemctl is-active --quiet "$SERVICE_NAME"; then
log_message "WARNING: Service $SERVICE_NAME is not running"
exit 1
fi
Get service cgroup path
CGROUP_PATH=$(systemctl show "$SERVICE_NAME" --property=ControlGroup --value)
SYS_CGROUP_PATH="/sys/fs/cgroup$CGROUP_PATH"
if [ ! -d "$SYS_CGROUP_PATH" ]; then
log_message "ERROR: Cgroup path not found: $SYS_CGROUP_PATH"
exit 1
fi
CPU monitoring
if [ -f "$SYS_CGROUP_PATH/cpu.stat" ]; then
CPU_USAGE_USEC=$(grep "usage_usec" "$SYS_CGROUP_PATH/cpu.stat" | cut -d' ' -f2)
CPU_THROTTLED_USEC=$(grep "throttled_usec" "$SYS_CGROUP_PATH/cpu.stat" | cut -d' ' -f2)
# Calculate CPU usage percentage (simplified)
if [ -n "$CPU_THROTTLED_USEC" ] && [ "$CPU_THROTTLED_USEC" -gt 0 ]; then
CPU_THROTTLE_PERCENT=$((CPU_THROTTLED_USEC * 100 / CPU_USAGE_USEC))
if [ "$CPU_THROTTLE_PERCENT" -gt "$CPU_THRESHOLD" ]; then
ALERT_MSG="CPU throttling detected: ${CPU_THROTTLE_PERCENT}% for service $SERVICE_NAME"
log_message "ALERT: $ALERT_MSG"
fi
fi
fi
Memory monitoring
if [ -f "$SYS_CGROUP_PATH/memory.current" ] && [ -f "$SYS_CGROUP_PATH/memory.max" ]; then
MEMORY_CURRENT=$(cat "$SYS_CGROUP_PATH/memory.current")
MEMORY_MAX=$(cat "$SYS_CGROUP_PATH/memory.max")
if [ "$MEMORY_MAX" != "max" ]; then
MEMORY_PERCENT=$((MEMORY_CURRENT * 100 / MEMORY_MAX))
MEMORY_MB=$((MEMORY_CURRENT / 1024 / 1024))
MEMORY_MAX_MB=$((MEMORY_MAX / 1024 / 1024))
log_message "INFO: Memory usage: ${MEMORY_MB}MB / ${MEMORY_MAX_MB}MB (${MEMORY_PERCENT}%)"
if [ "$MEMORY_PERCENT" -gt "$MEMORY_THRESHOLD" ]; then
ALERT_MSG="High memory usage: ${MEMORY_PERCENT}% (${MEMORY_MB}MB/${MEMORY_MAX_MB}MB) for service $SERVICE_NAME"
log_message "ALERT: $ALERT_MSG"
# Send email alert with cooldown
if [ ! -f "$COOLDOWN_FILE" ] || [ $(($(date +%s) - $(stat -c %Y "$COOLDOWN_FILE" 2>/dev/null || echo 0))) -gt "$ALERT_COOLDOWN" ]; then
echo "$ALERT_MSG" | mail -s "Resource Alert: $SERVICE_NAME" "$ALERT_EMAIL" 2>/dev/null
touch "$COOLDOWN_FILE"
fi
fi
fi
fi
I/O monitoring
if [ -f "$SYS_CGROUP_PATH/io.stat" ]; then
IO_STATS=$(cat "$SYS_CGROUP_PATH/io.stat")
if [ -n "$IO_STATS" ]; then
READ_BYTES=$(echo "$IO_STATS" | grep -E '^[0-9]+:[0-9]+ rbytes=' | head -1 | grep -o 'rbytes=[0-9]*' | cut -d'=' -f2)
WRITE_BYTES=$(echo "$IO_STATS" | grep -E '^[0-9]+:[0-9]+ wbytes=' | head -1 | grep -o 'wbytes=[0-9]*' | cut -d'=' -f2)
if [ -n "$READ_BYTES" ] && [ -n "$WRITE_BYTES" ]; then
READ_MB=$((READ_BYTES / 1024 / 1024))
WRITE_MB=$((WRITE_BYTES / 1024 / 1024))
log_message "INFO: I/O usage: Read ${READ_MB}MB, Write ${WRITE_MB}MB"
fi
fi
fi
Task monitoring
if [ -f "$SYS_CGROUP_PATH/pids.current" ] && [ -f "$SYS_CGROUP_PATH/pids.max" ]; then
TASKS_CURRENT=$(cat "$SYS_CGROUP_PATH/pids.current")
TASKS_MAX=$(cat "$SYS_CGROUP_PATH/pids.max")
log_message "INFO: Tasks: $TASKS_CURRENT / $TASKS_MAX"
if [ "$TASKS_MAX" != "max" ]; then
TASKS_PERCENT=$((TASKS_CURRENT * 100 / TASKS_MAX))
if [ "$TASKS_PERCENT" -gt 80 ]; then
log_message "ALERT: High task count: ${TASKS_PERCENT}% ($TASKS_CURRENT/$TASKS_MAX) for service $SERVICE_NAME"
fi
fi
fi
sudo chmod 755 /opt/testapp/monitor-resources.sh
sudo mkdir -p /var/log
sudo touch /var/log/resource-monitor.log
sudo chown syslog:adm /var/log/resource-monitor.log
Create systemd timer for automated monitoring
Set up a systemd timer to run the monitoring script at regular intervals, providing continuous resource oversight.
[Unit]
Description=Resource Monitor for Applications
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/opt/testapp/monitor-resources.sh
User=root
StandardOutput=journal
StandardError=journal
[Unit]
Description=Run Resource Monitor every 2 minutes
Requires=resource-monitor.service
[Timer]
OnCalendar=::0/2
Persistent=true
[Install]
WantedBy=timers.target
Enable and start services
Start the test application and monitoring system, then enable automatic startup on boot.
sudo systemctl daemon-reload
sudo systemctl enable --now testapp.service
sudo systemctl enable --now resource-monitor.timer
sudo systemctl status testapp.service
sudo systemctl status resource-monitor.timer
Create manual monitoring commands
Set up convenient commands for real-time resource monitoring and analysis.
#!/bin/bash
if [ $# -eq 0 ]; then
echo "Usage: $0 "
echo "Example: $0 testapp"
exit 1
fi
SERVICE_NAME="$1"
if ! systemctl is-active --quiet "$SERVICE_NAME"; then
echo "Service $SERVICE_NAME is not running"
exit 1
fi
CGROUP_PATH=$(systemctl show "$SERVICE_NAME" --property=ControlGroup --value)
SYS_CGROUP_PATH="/sys/fs/cgroup$CGROUP_PATH"
echo "=== Resource Usage for $SERVICE_NAME ==="
echo "Cgroup Path: $SYS_CGROUP_PATH"
echo
CPU Information
echo "CPU Usage:"
if [ -f "$SYS_CGROUP_PATH/cpu.stat" ]; then
cat "$SYS_CGROUP_PATH/cpu.stat"
else
echo "CPU stats not available"
fi
echo
Memory Information
echo "Memory Usage:"
if [ -f "$SYS_CGROUP_PATH/memory.current" ]; then
CURRENT=$(cat "$SYS_CGROUP_PATH/memory.current")
MAX=$(cat "$SYS_CGROUP_PATH/memory.max" 2>/dev/null || echo "unlimited")
echo "Current: $((CURRENT / 1024 / 1024)) MB"
if [ "$MAX" != "max" ] && [ "$MAX" != "unlimited" ]; then
echo "Limit: $((MAX / 1024 / 1024)) MB"
echo "Usage: $((CURRENT * 100 / MAX))%"
else
echo "Limit: unlimited"
fi
fi
echo
I/O Information
echo "I/O Usage:"
if [ -f "$SYS_CGROUP_PATH/io.stat" ]; then
cat "$SYS_CGROUP_PATH/io.stat"
else
echo "I/O stats not available"
fi
echo
Task Information
echo "Task Count:"
if [ -f "$SYS_CGROUP_PATH/pids.current" ]; then
CURRENT_TASKS=$(cat "$SYS_CGROUP_PATH/pids.current")
MAX_TASKS=$(cat "$SYS_CGROUP_PATH/pids.max" 2>/dev/null || echo "unlimited")
echo "Current: $CURRENT_TASKS"
echo "Limit: $MAX_TASKS"
fi
sudo chmod 755 /usr/local/bin/check-cgroup-resources
Verify your setup
Test the monitoring system and verify that resource limits are being enforced correctly.
# Check service status
sudo systemctl status testapp.service
View real-time resource usage
check-cgroup-resources testapp
Monitor system resources
sudo systemctl show testapp.service --property=CPUUsageNSec,MemoryCurrent,TasksCurrent
Check monitoring logs
sudo tail -f /var/log/resource-monitor.log
View timer status
sudo systemctl list-timers resource-monitor.timer
Advanced monitoring configuration
Configure email notifications
Set up email alerts for critical resource threshold breaches. This assumes you have a mail server configured.
# Configure postfix for local mail delivery
sudo dpkg-reconfigure postfix
For production environments, integrate with your existing SMTP server or monitoring system like Prometheus and Grafana for more sophisticated alerting.
Create dashboard script
Build a comprehensive dashboard for monitoring multiple services simultaneously.
#!/bin/bash
SERVICES=("testapp" "nginx" "postgresql")
while true; do
clear
echo "=== System Resource Dashboard ==="
echo "$(date)"
echo
for service in "${SERVICES[@]}"; do
if systemctl is-active --quiet "$service" 2>/dev/null; then
echo "[$service] - RUNNING"
CGROUP_PATH=$(systemctl show "$service" --property=ControlGroup --value 2>/dev/null)
if [ -n "$CGROUP_PATH" ]; then
SYS_CGROUP_PATH="/sys/fs/cgroup$CGROUP_PATH"
# Memory usage
if [ -f "$SYS_CGROUP_PATH/memory.current" ]; then
CURRENT=$(cat "$SYS_CGROUP_PATH/memory.current")
echo " Memory: $((CURRENT / 1024 / 1024)) MB"
fi
# Task count
if [ -f "$SYS_CGROUP_PATH/pids.current" ]; then
TASKS=$(cat "$SYS_CGROUP_PATH/pids.current")
echo " Tasks: $TASKS"
fi
fi
echo
else
echo "[$service] - STOPPED"
echo
fi
done
echo "Press Ctrl+C to exit"
sleep 5
done
sudo chmod 755 /usr/local/bin/resource-dashboard
Production deployment considerations
For production deployments, consider these additional configurations:
- Integrate monitoring with your existing observability stack
- Use more sophisticated alerting rules based on historical usage patterns
- Implement gradual limit enforcement with warning thresholds
- Set up log rotation for monitoring logs to prevent disk space issues
- Configure backup notification channels beyond email
You can extend this monitoring approach to work with container orchestration systems or integrate with CPU scheduling optimization for more comprehensive resource management.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Monitoring script shows "Cgroup path not found" | Service not using cgroups v2 or systemd | Verify service is managed by systemd: systemctl show service --property=ControlGroup |
| CPU limits not enforced | CPUAccounting disabled | Add CPUAccounting=true to service unit and reload |
| Memory alerts not working | Memory accounting disabled | Enable with MemoryAccounting=true in service unit |
| Email alerts not sending | Mail system not configured | Install and configure postfix: sudo apt install postfix mailutils |
| Timer not running monitoring | Timer service not enabled | Enable timer: sudo systemctl enable --now resource-monitor.timer |
| Resource limits too restrictive | Insufficient resources allocated | Analyze usage patterns and adjust limits in service unit |
Next steps
- Configure system resource limits with ulimit for additional process controls
- Set up comprehensive system monitoring with collectd for historical data collection
- Configure Prometheus alerting for cgroup metrics for enterprise monitoring
- Implement Kubernetes resource management for containerized workloads
- Automate system maintenance tasks using systemd timers
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
SERVICE_NAME="testapp"
ALERT_EMAIL="${1:-admin@localhost}"
MONITOR_INTERVAL="${2:-60}"
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Usage message
usage() {
echo "Usage: $0 [ALERT_EMAIL] [MONITOR_INTERVAL_SECONDS]"
echo "Example: $0 admin@example.com 30"
exit 1
}
# Cleanup on failure
cleanup() {
log_error "Installation failed. Cleaning up..."
systemctl stop "$SERVICE_NAME" 2>/dev/null || true
systemctl disable "$SERVICE_NAME" 2>/dev/null || true
rm -f "/etc/systemd/system/${SERVICE_NAME}.service"
rm -rf /opt/testapp
rm -f /usr/local/bin/resource-monitor.sh
rm -f "/etc/cron.d/resource-monitor"
}
trap cleanup ERR
# Check root privileges
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root"
exit 1
fi
# Detect OS and package manager
echo "[1/8] Detecting operating system..."
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
MAIL_PKG="mailutils"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
MAIL_PKG="mailx"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
MAIL_PKG="mailx"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_info "Detected: $PRETTY_NAME"
else
log_error "Cannot detect OS distribution"
exit 1
fi
# Verify cgroups v2 support
echo "[2/8] Verifying cgroups v2 support..."
if ! mount | grep -q "cgroup2"; then
log_error "cgroups v2 not detected. Add 'systemd.unified_cgroup_hierarchy=1' to kernel boot parameters and reboot"
exit 1
fi
log_info "cgroups v2 detected"
# Install required packages
echo "[3/8] Installing monitoring tools..."
$PKG_UPDATE
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL cgroup-tools systemd-cgroup-utils htop iotop sysstat $MAIL_PKG
else
$PKG_INSTALL libcgroup-tools systemd htop iotop sysstat $MAIL_PKG
fi
log_info "Packages installed successfully"
# Create test application
echo "[4/8] Creating test application..."
mkdir -p /opt/testapp
cat > /opt/testapp/cpu-intensive.sh << 'EOF'
#!/bin/bash
while true; do
# Generate some CPU load
for i in {1..1000}; do
echo "scale=5000; 4*a(1)" | bc -l > /dev/null 2>&1 || true
done
sleep 2
done
EOF
chmod 755 /opt/testapp/cpu-intensive.sh
chown -R root:root /opt/testapp
log_info "Test application created"
# Create systemd service with resource limits
echo "[5/8] Creating systemd service with resource limits..."
cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF
[Unit]
Description=Test Application for Resource Monitoring
After=network.target
[Service]
Type=simple
User=nobody
Group=nobody
ExecStart=/opt/testapp/cpu-intensive.sh
Restart=always
RestartSec=10
# CPU Limits - 50% of one CPU core
CPUQuota=50%
CPUWeight=100
# Memory Limits - 512MB
MemoryMax=512M
MemoryHigh=400M
# Task limits
TasksMax=50
# Enable accounting
CPUAccounting=true
MemoryAccounting=true
IOAccounting=true
TasksAccounting=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable "$SERVICE_NAME"
log_info "Service created and enabled"
# Create monitoring script
echo "[6/8] Creating resource monitoring script..."
cat > /usr/local/bin/resource-monitor.sh << EOF
#!/bin/bash
# Configuration
SERVICE_NAME="$SERVICE_NAME"
ALERT_EMAIL="$ALERT_EMAIL"
CPU_THRESHOLD=80
MEMORY_THRESHOLD=90
LOG_FILE="/var/log/resource-monitor.log"
ALERT_COOLDOWN=300
COOLDOWN_FILE="/tmp/alert-cooldown-\${SERVICE_NAME}"
# Logging function
log_message() {
echo "\$(date '+%Y-%m-%d %H:%M:%S') - \$1" >> "\$LOG_FILE"
}
# Check cooldown
check_cooldown() {
if [ -f "\$COOLDOWN_FILE" ]; then
LAST_ALERT=\$(cat "\$COOLDOWN_FILE")
CURRENT_TIME=\$(date +%s)
if [ \$((CURRENT_TIME - LAST_ALERT)) -lt \$ALERT_COOLDOWN ]; then
return 1
fi
fi
return 0
}
# Send alert
send_alert() {
if check_cooldown; then
echo "\$1" | mail -s "Resource Alert: \$SERVICE_NAME" "\$ALERT_EMAIL" 2>/dev/null || true
date +%s > "\$COOLDOWN_FILE"
log_message "ALERT: \$1"
fi
}
# Check if service is running
if ! systemctl is-active --quiet "\$SERVICE_NAME"; then
log_message "WARNING: Service \$SERVICE_NAME is not running"
exit 1
fi
# Get service cgroup path
CGROUP_PATH=\$(systemctl show "\$SERVICE_NAME" --property=ControlGroup --value)
SYS_CGROUP_PATH="/sys/fs/cgroup\$CGROUP_PATH"
if [ ! -d "\$SYS_CGROUP_PATH" ]; then
log_message "ERROR: Cgroup path not found: \$SYS_CGROUP_PATH"
exit 1
fi
# Memory monitoring
if [ -f "\$SYS_CGROUP_PATH/memory.current" ] && [ -f "\$SYS_CGROUP_PATH/memory.max" ]; then
MEMORY_CURRENT=\$(cat "\$SYS_CGROUP_PATH/memory.current")
MEMORY_MAX=\$(cat "\$SYS_CGROUP_PATH/memory.max")
if [ "\$MEMORY_MAX" != "max" ]; then
MEMORY_PERCENT=\$((MEMORY_CURRENT * 100 / MEMORY_MAX))
if [ \$MEMORY_PERCENT -gt \$MEMORY_THRESHOLD ]; then
send_alert "Memory usage high: \${MEMORY_PERCENT}% (\${MEMORY_CURRENT} bytes) for service \$SERVICE_NAME"
fi
log_message "Memory: \${MEMORY_PERCENT}% used (\${MEMORY_CURRENT}/\${MEMORY_MAX} bytes)"
fi
fi
# CPU throttling check
if [ -f "\$SYS_CGROUP_PATH/cpu.stat" ]; then
CPU_THROTTLED=\$(grep "throttled_usec" "\$SYS_CGROUP_PATH/cpu.stat" | cut -d' ' -f2)
CPU_USAGE=\$(grep "usage_usec" "\$SYS_CGROUP_PATH/cpu.stat" | cut -d' ' -f2)
if [ -n "\$CPU_THROTTLED" ] && [ "\$CPU_THROTTLED" -gt 0 ] && [ "\$CPU_USAGE" -gt 0 ]; then
CPU_THROTTLE_PERCENT=\$((CPU_THROTTLED * 100 / CPU_USAGE))
if [ \$CPU_THROTTLE_PERCENT -gt \$CPU_THRESHOLD ]; then
send_alert "CPU throttling detected: \${CPU_THROTTLE_PERCENT}% for service \$SERVICE_NAME"
fi
log_message "CPU throttling: \${CPU_THROTTLE_PERCENT}%"
fi
fi
log_message "Monitoring check completed for \$SERVICE_NAME"
EOF
chmod 755 /usr/local/bin/resource-monitor.sh
chown root:root /usr/local/bin/resource-monitor.sh
log_info "Monitoring script created"
# Create cron job
echo "[7/8] Setting up monitoring cron job..."
cat > /etc/cron.d/resource-monitor << EOF
# Resource monitoring for $SERVICE_NAME
*/$MONITOR_INTERVAL * * * * root /usr/local/bin/resource-monitor.sh
EOF
chmod 644 /etc/cron.d/resource-monitor
chown root:root /etc/cron.d/resource-monitor
log_info "Cron job configured to run every $MONITOR_INTERVAL seconds"
# Start service and verify
echo "[8/8] Starting service and verifying installation..."
systemctl start "$SERVICE_NAME"
sleep 3
if systemctl is-active --quiet "$SERVICE_NAME"; then
log_info "Service started successfully"
else
log_error "Service failed to start"
exit 1
fi
# Create log file with proper permissions
touch /var/log/resource-monitor.log
chmod 644 /var/log/resource-monitor.log
chown root:root /var/log/resource-monitor.log
# Run initial monitoring check
/usr/local/bin/resource-monitor.sh
# Verification
log_info "Installation completed successfully!"
echo ""
echo "Service status:"
systemctl status "$SERVICE_NAME" --no-pager -l
echo ""
echo "Monitor service: systemctl status $SERVICE_NAME"
echo "View logs: tail -f /var/log/resource-monitor.log"
echo "Test monitoring: /usr/local/bin/resource-monitor.sh"
echo "Alerts will be sent to: $ALERT_EMAIL"
Review the script before running. Execute with: bash install.sh