#!/bin/bash
# ==============================================================================================
# ============================= System Tuning Monitor ==========================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# inotify and PHP-FPM utilisation tracking via time-series snapshots. Scheduled
# every 6 hours (0 */6 * * *). Writes one bounded log entry per run to
# TUNING_MONITOR_LOG. weekly_health_digest.sh reads this log to report peak,
# average, and warning counts over the week.
#
# Background snapshot script — no output when healthy. Warns (and notifies) only
# when INOTIFY_WARN_PCT or PHP_FPM_WARN_PCT thresholds are exceeded.
#
# inotify exhaustion symptoms: downloads complete but arrs don't detect them,
# live TV stutter, library updates stop. PHP-FPM exhaustion symptoms: unRAID
# WebGUI slowdowns or timeouts under load.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Snapshot for Trend, Not Just Alert
#   Exhaustion events are rarely instant — they build over hours or days.
#   Logging every 6 hours builds a trend that weekly_health_digest.sh can
#   surface as a warning count, catching gradual pressure before it becomes
#   an outage.
#
# Bounded Log Size
#   Log entries are trimmed to TUNING_LOG_RETENTION days on every write.
#   The log never grows unbounded regardless of how long the server runs.
#
# Silent When Healthy
#   No output, no notification on a clean run. Threshold breach is the only
#   signal — routine snapshots below the threshold produce nothing.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Each run snapshots:
#   inotify: instances in use vs INOTIFY_MAX_INSTANCES kernel limit.
#            Top 5 consumers by instance count. Warns above INOTIFY_WARN_PCT.
#   php-fpm: active workers vs PHP_MAX_CHILDREN limit.
#            Warns above PHP_FPM_WARN_PCT.
#
# Log line format (one per run, trimmed to TUNING_LOG_RETENTION days):
#   DATE|TIME|INOTIFY_USED|INOTIFY_LIMIT|INOTIFY_PCT|INOTIFY_WARN|
#   PHPFPM_ACTIVE|PHPFPM_MAX|PHPFPM_PCT|PHPFPM_WARN
# INOTIFY_WARN and PHPFPM_WARN are 1/0 flags. weekly_health_digest.sh counts
# warnings over the week to show trend severity.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Single Instance Lock
#   acquire_lock prevents overlapping 6-hour snapshot runs.
#
# Root Enforcement
#   /proc/*/fd enumeration for inotify consumer counting requires root access.
#
# Atomic Log Write
#   Trim uses tmp file + mv — partial writes during log rotation cannot corrupt
#   the accumulated history.
#
# ==============================================================================================
# STATE FILES
# ==============================================================================================
#
#   TUNING_MONITOR_LOG   — DATA_DIR/tuning_monitor.db
#       One line per 6-hour snapshot. Trimmed to TUNING_LOG_RETENTION days on
#       each write — bounded size. Read by weekly_health_digest.sh for trend
#       reporting. Resets if DATA_DIR is cleared.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
#   INOTIFY_WARN_PCT
#       Warn if inotify instances exceed this percentage of the kernel limit. (default: 80)
#
#   PHP_FPM_WARN_PCT
#       Warn if php-fpm active workers exceed this percentage of PHP_MAX_CHILDREN. (default: 80)
#
#   PHP_MAX_CHILDREN
#       Maximum php-fpm workers — set by php_fpm_max_children.sh in System_Essentials.
#
#   TUNING_MONITOR_LOG
#       Log file path. (default: DATA_DIR/tuning_monitor.db)
#
#   TUNING_LOG_RETENTION
#       Days before old entries are purged. (default: 30)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# system_tuning_monitor.sh
#     Take snapshot. Write to log. Warn (and notify) if thresholds exceeded.
#     Silent when healthy.
#
# system_tuning_monitor.sh --dry-run
#     Measure inotify and PHP-FPM utilisation and display results. No log write.
#
# system_tuning_monitor.sh --status
#     Show thresholds, log path, and retention. Then exit.
#
# system_tuning_monitor.sh --log
#     Verbose output during the snapshot including top inotify consumers.
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "$SCRIPT_DIR/../load_config.sh"

parse_args "$@"

# ==============================================================================================
# ━━━ Setup ━━━
# ==============================================================================================
if [[ "$EUID" -ne 0 ]]; then
    error "Must be run as root — /proc/*/fd requires root access"
    exit 1
fi


acquire_lock

# detect_hosts() sets MY_ID — used in warning output
detect_hosts

log "$ICON_GEAR Config: inotify-warn=${INOTIFY_WARN_PCT:-80}% php-fpm-warn=${PHP_FPM_WARN_PCT:-80}% max-workers=${PHP_MAX_CHILDREN:-250} retention=${TUNING_LOG_RETENTION:-30}d"

DATE=$(date '+%Y-%m-%d')
TIME=$(date '+%H:%M')
INOTIFY_WARN=0
PHPFPM_WARN=0

# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
    echo ""
    echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
    echo "$ICON_HOST   Identity:       $MY_ID ($LOCAL_SERVER_NAME)"
    echo "$ICON_GEAR   inotify warn:   ${INOTIFY_WARN_PCT:-80}%"
    echo "$ICON_GEAR   php-fpm warn:   ${PHP_FPM_WARN_PCT:-80}%"
    echo "$ICON_GEAR   php-fpm max:    ${PHP_MAX_CHILDREN:-250}"
    echo "$ICON_GEAR   Log file:       ${TUNING_MONITOR_LOG:-not set}"
    echo "$ICON_GEAR   Retention:      ${TUNING_LOG_RETENTION:-30} days"
    echo "$ICON_GEAR   Dry Run:        $DRY_RUN"

    if [[ -f "$TUNING_MONITOR_LOG" ]]; then
        ENTRY_COUNT=$(wc -l < "$TUNING_MONITOR_LOG")
        OLDEST=$(awk -F'|' 'NR==1{print $1}' "$TUNING_MONITOR_LOG")
        NEWEST=$(awk -F'|' 'END{print $1}' "$TUNING_MONITOR_LOG")
        echo "$ICON_MONITOR   Log entries:    $ENTRY_COUNT ($OLDEST → $NEWEST)"
    else
        echo "$ICON_MONITOR   Log entries:    none yet"
    fi
    echo "━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
fi

[[ "$DRY_RUN" == true ]] && warn "DRY RUN — measuring only, no log write"

# ==============================================================================================
# ━━━ inotify ━━━
# ==============================================================================================
INOTIFY_LIMIT=$(sysctl -n fs.inotify.max_user_instances 2>/dev/null || echo 0)
INOTIFY_USED=$(find /proc/*/fd -lname 'anon_inode:inotify' 2>/dev/null | wc -l)
INOTIFY_USED="${INOTIFY_USED//[^0-9]/}"
INOTIFY_USED="${INOTIFY_USED:-0}"

if [[ "$INOTIFY_LIMIT" -gt 0 ]]; then
    INOTIFY_PCT=$(( INOTIFY_USED * 100 / INOTIFY_LIMIT ))
else
    INOTIFY_PCT=0
fi

[[ "$INOTIFY_PCT" -ge "${INOTIFY_WARN_PCT:-80}" ]] && INOTIFY_WARN=1

# Top 5 inotify consumers
INOTIFY_TOP=$(find /proc/*/fd -lname 'anon_inode:inotify' 2>/dev/null | \
    awk -F/ '{print $3}' | sort | uniq -c | sort -rn | head -5 | \
    while read -r count pid; do
        comm=$(cat "/proc/$pid/comm" 2>/dev/null || echo "?")
        echo "${count}×${comm}"
    done | tr '\n' ',' | sed 's/,$//')

if [[ "$INOTIFY_WARN" -eq 1 ]]; then
    warn "$MY_ID — inotify: ${INOTIFY_USED}/${INOTIFY_LIMIT} (${INOTIFY_PCT}%) — above ${INOTIFY_WARN_PCT}% threshold"
    warn "Top consumers: ${INOTIFY_TOP:-unknown}"
    warn "Symptoms: containers missing file events, library not updating, Live TV stutter"
    notify "inotify at ${INOTIFY_PCT}% on $(hostname) — ${INOTIFY_USED}/${INOTIFY_LIMIT} in use — top: ${INOTIFY_TOP}" \
        "System Tuning" "warning"
else
    log "inotify: ${INOTIFY_USED}/${INOTIFY_LIMIT} (${INOTIFY_PCT}%) ✅"
    log "inotify top consumers: ${INOTIFY_TOP:-none}"
fi

# ==============================================================================================
# ━━━ php-fpm ━━━
# ==============================================================================================
PHPFPM_MAX="${PHP_MAX_CHILDREN:-250}"

PHPFPM_ACTIVE=$(ps aux 2>/dev/null | grep -c "php-fpm: pool" || echo 0)
PHPFPM_ACTIVE="${PHPFPM_ACTIVE//[^0-9]/}"
PHPFPM_ACTIVE="${PHPFPM_ACTIVE:-0}"

if [[ "$PHPFPM_MAX" -gt 0 ]]; then
    PHPFPM_PCT=$(( PHPFPM_ACTIVE * 100 / PHPFPM_MAX ))
else
    PHPFPM_PCT=0
fi

[[ "$PHPFPM_PCT" -ge "${PHP_FPM_WARN_PCT:-80}" ]] && PHPFPM_WARN=1

if [[ "$PHPFPM_WARN" -eq 1 ]]; then
    warn "$MY_ID — php-fpm: ${PHPFPM_ACTIVE}/${PHPFPM_MAX} workers (${PHPFPM_PCT}%) — above ${PHP_FPM_WARN_PCT}% threshold"
    warn "Symptom: unRAID WebGUI slowdowns or timeouts under load"
    notify "php-fpm at ${PHPFPM_PCT}% on $(hostname) — ${PHPFPM_ACTIVE}/${PHPFPM_MAX} workers active" \
        "System Tuning" "warning"
else
    log "php-fpm: ${PHPFPM_ACTIVE}/${PHPFPM_MAX} active workers (${PHPFPM_PCT}%) ✅"
fi

# ==============================================================================================
# ━━━ Write Snapshot ━━━
# ==============================================================================================
if [[ -z "${TUNING_MONITOR_LOG:-}" ]]; then
    warn "TUNING_MONITOR_LOG not set — snapshot not written"
    exit 0
fi

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — snapshot not written"
    warn "Would write: ${DATE}|${TIME}|${INOTIFY_USED}|${INOTIFY_LIMIT}|${INOTIFY_PCT}|${INOTIFY_WARN}|${PHPFPM_ACTIVE}|${PHPFPM_MAX}|${PHPFPM_PCT}|${PHPFPM_WARN}"
    exit 0
fi

mkdir -p "$(dirname "$TUNING_MONITOR_LOG")"

# Trim old entries — atomic write via temp file
if [[ -f "$TUNING_MONITOR_LOG" ]]; then
    CUTOFF=$(date -d "${TUNING_LOG_RETENTION:-30} days ago" '+%Y-%m-%d')
    awk -F'|' -v cutoff="$CUTOFF" '$1 >= cutoff' \
        "$TUNING_MONITOR_LOG" > "${TUNING_MONITOR_LOG}.tmp" && \
        mv "${TUNING_MONITOR_LOG}.tmp" "$TUNING_MONITOR_LOG"
fi

# Append snapshot
echo "${DATE}|${TIME}|${INOTIFY_USED}|${INOTIFY_LIMIT}|${INOTIFY_PCT}|${INOTIFY_WARN}|${PHPFPM_ACTIVE}|${PHPFPM_MAX}|${PHPFPM_PCT}|${PHPFPM_WARN}" \
    >> "$TUNING_MONITOR_LOG"

echo "Snapshot written: inotify ${INOTIFY_PCT}% php-fpm ${PHPFPM_PCT}%"
log "Entry: ${DATE}|${TIME}|${INOTIFY_USED}/${INOTIFY_LIMIT}(${INOTIFY_PCT}%,warn=${INOTIFY_WARN})|${PHPFPM_ACTIVE}/${PHPFPM_MAX}(${PHPFPM_PCT}%,warn=${PHPFPM_WARN})"