transcode manager, symlink repair and more
This commit is contained in:
+31
-13
@@ -649,39 +649,57 @@ MEDIA_MAINTENANCE_JOBS=(
|
||||
# ==============================================================================================
|
||||
# ── TRANSCODES ────────────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
# Session-based storage allocator using filesystem indirection.
|
||||
# ffmpeg resolves the symlink path ONCE at session start — existing sessions are never affected
|
||||
# by symlink changes. Only NEW sessions care about where the symlink currently points.
|
||||
# Session-based storage allocator using filesystem symlink indirection.
|
||||
# ffmpeg resolves the symlink ONCE at session start — existing sessions are never affected.
|
||||
# Only new sessions care about where the symlink currently points.
|
||||
#
|
||||
# How it works:
|
||||
# ramdisk_setup.sh — run once at array start, creates tmpfs and sets symlink
|
||||
# transcode_manager.sh — every 3 min, monitors ramdisk usage and flips symlink if needed
|
||||
# transcode_manager.sh — every 3 min, monitors usage and manages symlink direction
|
||||
# transcode_cleanup.sh — every 5 min, removes old inactive files from both locations
|
||||
#
|
||||
# Hysteresis gap between RAMDISK_WARN_GB and RAMDISK_LOW_GB prevents flip-flop
|
||||
# when usage hovers near the threshold. Gap should be at least 0.5-1GB.
|
||||
# Ramdisk is dynamic tmpfs — only uses RAM actually needed, RAMDISK_SIZE is the ceiling.
|
||||
# ⚠️ Docker mount warning:
|
||||
# Do NOT add a static SSD transcode path as a second volume mount in your Emby container.
|
||||
# If the SSD path is mounted inside the container Emby can see it and will use it
|
||||
# independently of the symlink — breaking symlink-based routing entirely.
|
||||
# The symlink IS your emergency lever — one mount only:
|
||||
# /mnt/ram-transcode → /ext-ram-transcode
|
||||
|
||||
RAMDISK_PATH="/mnt/ramdisk_transcodes" # tmpfs mount point created at array start
|
||||
RAMDISK_SIZE="8G" # ceiling — tmpfs only uses RAM actually needed
|
||||
TRANSCODE_LINK="/mnt/ram-transcode" # symlink Emby points at — location never changes
|
||||
TRANSCODE_SSD="/mnt/cache/Temp_Storage/Emby/Transcodes/" # SSD fallback for edge cases only
|
||||
TRANSCODE_SSD="/mnt/cache/Temp_Storage/Emby/Transcodes/" # SSD fallback location
|
||||
|
||||
# Usage thresholds in GB
|
||||
# Usage thresholds in GB — hysteresis gap prevents flip-flop near threshold
|
||||
RAMDISK_WARN_GB=6.8 # flip symlink to SSD at or above this usage
|
||||
RAMDISK_LOW_GB=5.0 # flip symlink back to ramdisk when usage drops here
|
||||
RAMDISK_LOW_GB=5.5 # flip symlink back to ramdisk when usage drops here
|
||||
RAMDISK_SSD_MIN_GB=20 # minimum free GB on SSD required before allowing flip to SSD
|
||||
|
||||
# Cleanup age thresholds — files must be older than these AND not open by any process
|
||||
TRANSCODE_MAX_AGE=20 # minutes before a transcode file is eligible for cleanup
|
||||
TRANSCODE_ORPHAN_AGE=30 # minutes before an orphaned file is eligible — extra caution buffer
|
||||
TRANSCODE_ORPHAN_AGE=30 # minutes before an orphaned file is eligible
|
||||
|
||||
# Flip frequency alert — too many flips per hour may indicate ramdisk needs to be larger
|
||||
TRANSCODE_FLIP_WARN=3 # notify if symlink flips this many times in one hour
|
||||
|
||||
# Permissions applied to ramdisk and SSD fallback — must match your Emby container user
|
||||
# Permissions — must match your Emby container user
|
||||
TRANSCODE_OWNER="nobody:users"
|
||||
TRANSCODE_MODE="755"
|
||||
TRANSCODE_CHMOD="755" # renamed from TRANSCODE_MODE to avoid ambiguity with manager mode
|
||||
|
||||
# Operating mode — controls symlink routing behavior
|
||||
# smart — auto-flips between ramdisk and SSD based on usage thresholds (default)
|
||||
# ramdisk — always uses ramdisk, never flips to SSD regardless of usage
|
||||
# useful when load is light and you want guaranteed ramdisk performance
|
||||
# warns if usage exceeds threshold but does not flip
|
||||
# ssd — always uses SSD, never uses ramdisk
|
||||
# useful during ramdisk maintenance, testing, or after a flip issue
|
||||
# switch to this mode to drain ramdisk sessions gracefully
|
||||
TRANSCODE_MANAGER_MODE="smart"
|
||||
|
||||
# Emby container check — skips threshold checks when Emby is not running
|
||||
# Prevents unnecessary symlink flips when no transcoding is happening
|
||||
TRANSCODE_CHECK_EMBY=true
|
||||
TRANSCODE_EMBY_CONTAINER="Emby" # exact Docker container name — case sensitive
|
||||
|
||||
# ==============================================================================================
|
||||
# ── MONITOR ───────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
+261
-205
@@ -2,21 +2,25 @@
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# --------------------------------- Transcode Manager ------------------------------------------
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# Monitors ramdisk usage and manages the transcode symlink direction.
|
||||
# New transcode sessions land wherever the symlink points — existing sessions are unaffected.
|
||||
# Run every 2-3 minutes via cron/User Scripts plugin.
|
||||
# Manages Emby transcode storage using filesystem symlink indirection.
|
||||
# ffmpeg resolves the symlink ONCE at session start — existing sessions are never affected.
|
||||
# Only new sessions care about where the symlink currently points.
|
||||
#
|
||||
# Logic:
|
||||
# Ramdisk not found → ensure symlink points to SSD, notify
|
||||
# Usage < RAMDISK_LOW_GB → ensure symlink points to ramdisk
|
||||
# Usage >= RAMDISK_WARN_GB → flip symlink to SSD (after checking SSD has space)
|
||||
# Only notifies on STATE CHANGE — silent when nothing changes
|
||||
# Three operating modes — set TRANSCODE_MANAGER_MODE in Master.conf:
|
||||
# smart — auto-flips between ramdisk and SSD based on usage thresholds (default)
|
||||
# ramdisk — always uses ramdisk, never flips to SSD regardless of usage
|
||||
# ssd — always uses SSD, never uses ramdisk
|
||||
#
|
||||
# Hysteresis gap between RAMDISK_WARN_GB and RAMDISK_LOW_GB prevents flip-flop
|
||||
# when usage hovers near the threshold.
|
||||
# Safety checks on every run regardless of mode:
|
||||
# Ramdisk disappeared → auto-flip to SSD, notify warning
|
||||
# SSD path missing → disable SSD fallback, notify warning
|
||||
# Symlink broken → auto-recreate, notify
|
||||
# Symlink missing → auto-recreate, notify
|
||||
# Permissions drift → fix silently
|
||||
# Emby not running → skip threshold checks, verify symlink only
|
||||
#
|
||||
# State tracked in /tmp/transcode_state.db — resets on reboot (correct, ramdisk also resets)
|
||||
# All configuration in Master.conf under Transcode Manager section.
|
||||
# Supports --dry-run and --status.
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -26,8 +30,6 @@ source "$SCRIPT_DIR/../common.sh"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
STATE_FILE="/tmp/transcode_state.db"
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_GEAR Setup ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
@@ -41,140 +43,105 @@ fi
|
||||
|
||||
success "Running as root"
|
||||
|
||||
case "$TRANSCODE_MANAGER_MODE" in
|
||||
smart|ramdisk|ssd) info "$ICON_GEAR Mode: $TRANSCODE_MANAGER_MODE" ;;
|
||||
*)
|
||||
warn "Unknown TRANSCODE_MANAGER_MODE: $TRANSCODE_MANAGER_MODE — defaulting to smart"
|
||||
TRANSCODE_MANAGER_MODE="smart"
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_SUMMARY Status ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
if [[ "$SHOW_STATUS" == true ]]; then
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
|
||||
echo "$ICON_RAM Ramdisk path: $RAMDISK_PATH"
|
||||
echo "$ICON_RAM Warn at: ${RAMDISK_WARN_GB}GB"
|
||||
echo "$ICON_RAM Back to RAM at: ${RAMDISK_LOW_GB}GB"
|
||||
echo "$ICON_DISK SSD fallback: $TRANSCODE_SSD"
|
||||
echo "$ICON_DISK SSD min free: ${RAMDISK_SSD_MIN_GB}GB"
|
||||
echo "$ICON_GEAR Mode: $TRANSCODE_MANAGER_MODE"
|
||||
echo "$ICON_RAM Ramdisk: $RAMDISK_PATH ($RAMDISK_SIZE)"
|
||||
echo "$ICON_LINK Symlink: $TRANSCODE_LINK"
|
||||
echo "$ICON_NOTIFY Flip warn: $TRANSCODE_FLIP_WARN per hour"
|
||||
echo "$ICON_DISK SSD: $TRANSCODE_SSD"
|
||||
echo "$ICON_RAM Warn at: ${RAMDISK_WARN_GB}GB"
|
||||
echo "$ICON_RAM Low at: ${RAMDISK_LOW_GB}GB"
|
||||
echo "$ICON_DISK SSD min free: ${RAMDISK_SSD_MIN_GB}GB"
|
||||
echo "$ICON_RAM Flip warn: $TRANSCODE_FLIP_WARN per hour"
|
||||
echo "$ICON_GEAR Check Emby: $TRANSCODE_CHECK_EMBY ($TRANSCODE_EMBY_CONTAINER)"
|
||||
echo "$ICON_GEAR Dry Run: $DRY_RUN"
|
||||
|
||||
if [[ -f "$STATE_FILE" ]]; then
|
||||
echo ""
|
||||
echo "━━━ Current State ━━━"
|
||||
cat "$STATE_FILE"
|
||||
CURRENT_TARGET=$(readlink "$TRANSCODE_LINK" 2>/dev/null || echo "missing")
|
||||
echo "$ICON_LINK Symlink now: $TRANSCODE_LINK → $CURRENT_TARGET"
|
||||
if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
USED_KB=$(df "$RAMDISK_PATH" --output=used | tail -1 | tr -d ' ')
|
||||
USED_GB=$(awk "BEGIN {printf \"%.2f\", $USED_KB / 1048576}")
|
||||
echo "$ICON_RAM Ramdisk now: ${USED_GB}GB used"
|
||||
else
|
||||
echo "$ICON_RAM Ramdisk: NOT MOUNTED"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no symlink changes will be made"
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# STATE HELPERS
|
||||
# HELPERS
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
|
||||
get_state() {
|
||||
local key="$1"
|
||||
grep -E "^${key}=" "$STATE_FILE" 2>/dev/null | cut -d'=' -f2
|
||||
}
|
||||
|
||||
set_state() {
|
||||
local key="$1" value="$2"
|
||||
if [[ -f "$STATE_FILE" ]]; then
|
||||
grep -vE "^${key}=" "$STATE_FILE" > "${STATE_FILE}.tmp"
|
||||
echo "${key}=${value}" >> "${STATE_FILE}.tmp"
|
||||
mv "${STATE_FILE}.tmp" "$STATE_FILE"
|
||||
else
|
||||
echo "${key}=${value}" > "$STATE_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
init_state() {
|
||||
if [[ ! -f "$STATE_FILE" ]]; then
|
||||
local NOW
|
||||
NOW=$(date +%s)
|
||||
cat > "$STATE_FILE" <<EOF
|
||||
current_target=$RAMDISK_PATH
|
||||
last_flip_time=$NOW
|
||||
flip_count_hour=0
|
||||
flip_hour_start=$NOW
|
||||
EOF
|
||||
log "State file initialised"
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# FLIP TRACKING
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
track_flip() {
|
||||
local NOW
|
||||
NOW=$(date +%s)
|
||||
local HOUR_START
|
||||
HOUR_START=$(get_state "flip_hour_start")
|
||||
local FLIP_COUNT
|
||||
FLIP_COUNT=$(get_state "flip_count_hour")
|
||||
[[ -z "$FLIP_COUNT" ]] && FLIP_COUNT=0
|
||||
[[ -z "$HOUR_START" ]] && HOUR_START=$NOW
|
||||
|
||||
if (( NOW - HOUR_START >= 3600 )); then
|
||||
FLIP_COUNT=0
|
||||
set_state "flip_hour_start" "$NOW"
|
||||
fi
|
||||
|
||||
((FLIP_COUNT++))
|
||||
set_state "flip_count_hour" "$FLIP_COUNT"
|
||||
set_state "last_flip_time" "$NOW"
|
||||
|
||||
log "Symlink flip count this hour: $FLIP_COUNT"
|
||||
|
||||
if (( FLIP_COUNT >= TRANSCODE_FLIP_WARN )); then
|
||||
warn "$ICON_LINK Symlink has flipped $FLIP_COUNT times this hour — consider increasing ramdisk size"
|
||||
notify "Transcode symlink flipping frequently on $(hostname) — $FLIP_COUNT flips in 1hr — consider increasing RAMDISK_SIZE" "Transcode Manager" "warning"
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# SYMLINK MANAGEMENT
|
||||
# Sets the symlink to the desired target.
|
||||
# Only flips if current target differs — tracks flip for frequency monitoring.
|
||||
# Force mode skips the state check and always sets the symlink (used for recovery).
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
set_symlink() {
|
||||
local target="$1" force="${2:-false}"
|
||||
local current_target
|
||||
current_target=$(get_state "current_target")
|
||||
|
||||
if [[ "$force" == false && "$current_target" == "$target" ]]; then
|
||||
log "Symlink already points to $target — no change"
|
||||
return 0
|
||||
fi
|
||||
|
||||
flip_symlink() {
|
||||
local target="$1" reason="$2"
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would set symlink: $TRANSCODE_LINK → $target"
|
||||
warn "DRY RUN — would flip symlink to: $target ($reason)"
|
||||
return 0
|
||||
fi
|
||||
ln -sfn "$target" "$TRANSCODE_LINK"
|
||||
success "$ICON_LINK Symlink flipped to: $target ($reason)"
|
||||
}
|
||||
|
||||
info "$ICON_LINK Setting symlink: $TRANSCODE_LINK → $target"
|
||||
fix_permissions() {
|
||||
local path="$1"
|
||||
[[ ! -d "$path" ]] && return
|
||||
chown -R "$TRANSCODE_OWNER" "$path" 2>/dev/null
|
||||
chmod -R "$TRANSCODE_CHMOD" "$path" 2>/dev/null
|
||||
log "Permissions fixed on $path"
|
||||
}
|
||||
|
||||
# Ensure parent directory exists
|
||||
mkdir -p "$(dirname "$TRANSCODE_LINK")"
|
||||
get_ramdisk_used_gb() {
|
||||
local used_kb
|
||||
used_kb=$(df "$RAMDISK_PATH" --output=used 2>/dev/null | tail -1 | tr -d ' ')
|
||||
awk "BEGIN {printf \"%.2f\", ${used_kb:-0} / 1048576}"
|
||||
}
|
||||
|
||||
if ln -sfn "$target" "$TRANSCODE_LINK"; then
|
||||
success "$ICON_LINK Symlink set → $target"
|
||||
set_state "current_target" "$target"
|
||||
get_ramdisk_avail_gb() {
|
||||
local avail_kb
|
||||
avail_kb=$(df "$RAMDISK_PATH" --output=avail 2>/dev/null | tail -1 | tr -d ' ')
|
||||
awk "BEGIN {printf \"%.2f\", ${avail_kb:-0} / 1048576}"
|
||||
}
|
||||
|
||||
# Only track flip and notify if this was a real direction change
|
||||
if [[ "$current_target" != "$target" ]]; then
|
||||
track_flip
|
||||
if [[ "$target" == "$RAMDISK_PATH" ]]; then
|
||||
notify "Transcode symlink flipped to RAMDISK on $(hostname) — ramdisk has space" "Transcode Manager" "normal"
|
||||
else
|
||||
notify "Transcode symlink flipped to SSD on $(hostname) — ramdisk usage high" "Transcode Manager" "warning"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
error "Failed to set symlink: $TRANSCODE_LINK → $target"
|
||||
notify "Transcode symlink failed to set on $(hostname)" "Transcode Manager" "warning"
|
||||
return 1
|
||||
fi
|
||||
get_ssd_free_gb() {
|
||||
local free_kb
|
||||
free_kb=$(df "$TRANSCODE_SSD" --output=avail 2>/dev/null | tail -1 | tr -d ' ')
|
||||
awk "BEGIN {printf \"%.2f\", ${free_kb:-0} / 1048576}"
|
||||
}
|
||||
|
||||
get_flip_count() {
|
||||
local state_file="/tmp/transcode_flip_state.db"
|
||||
local current_hour
|
||||
current_hour=$(date '+%Y-%m-%d-%H')
|
||||
if [[ ! -f "$state_file" ]]; then echo "0"; return; fi
|
||||
local stored_hour stored_count
|
||||
stored_hour=$(awk -F'|' 'NR==1{print $1}' "$state_file" 2>/dev/null)
|
||||
stored_count=$(awk -F'|' 'NR==1{print $2}' "$state_file" 2>/dev/null)
|
||||
[[ "$stored_hour" == "$current_hour" ]] && echo "${stored_count:-0}" || echo "0"
|
||||
}
|
||||
|
||||
increment_flip_count() {
|
||||
local state_file="/tmp/transcode_flip_state.db"
|
||||
local current_hour
|
||||
current_hour=$(date '+%Y-%m-%d-%H')
|
||||
local current_count
|
||||
current_count=$(get_flip_count)
|
||||
current_count=$((current_count + 1))
|
||||
echo "${current_hour}|${current_count}" > "$state_file"
|
||||
echo "$current_count"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
@@ -182,132 +149,221 @@ set_symlink() {
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "━━━ $ICON_RAM Transcode Manager — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
|
||||
echo "$ICON_GEAR Mode: $TRANSCODE_MANAGER_MODE"
|
||||
echo ""
|
||||
|
||||
init_state
|
||||
START=$(date +%s)
|
||||
RAMDISK_HEALTHY=true
|
||||
SSD_HEALTHY=true
|
||||
EMBY_RUNNING=true
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# Symlink integrity check
|
||||
# If symlink is missing or target doesn't exist — force correct it immediately
|
||||
# CHECK 1 — Emby running
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
if [[ "$TRANSCODE_CHECK_EMBY" == true ]]; then
|
||||
info "$ICON_CONTAINERS Checking $TRANSCODE_EMBY_CONTAINER..."
|
||||
if ! docker inspect "$TRANSCODE_EMBY_CONTAINER" \
|
||||
--format '{{.State.Running}}' 2>/dev/null | grep -q "true"; then
|
||||
warn "$TRANSCODE_EMBY_CONTAINER is not running — skipping threshold checks"
|
||||
EMBY_RUNNING=false
|
||||
else
|
||||
success "$TRANSCODE_EMBY_CONTAINER is running"
|
||||
fi
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# CHECK 2 — Symlink integrity
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
info "$ICON_LINK Checking symlink integrity..."
|
||||
|
||||
SYMLINK_OK=true
|
||||
CURRENT_TARGET=$(readlink "$TRANSCODE_LINK" 2>/dev/null)
|
||||
|
||||
if [[ ! -L "$TRANSCODE_LINK" ]]; then
|
||||
error "$ICON_LINK Symlink missing: $TRANSCODE_LINK — recovering"
|
||||
SYMLINK_OK=false
|
||||
else
|
||||
LINK_TARGET=$(readlink "$TRANSCODE_LINK" 2>/dev/null)
|
||||
if [[ ! -d "$LINK_TARGET" ]]; then
|
||||
error "$ICON_LINK Symlink target does not exist: $LINK_TARGET — recovering"
|
||||
SYMLINK_OK=false
|
||||
else
|
||||
info "$ICON_LINK Symlink valid: $TRANSCODE_LINK → $LINK_TARGET"
|
||||
if [[ -z "$CURRENT_TARGET" ]]; then
|
||||
warn "$ICON_LINK Symlink missing — recreating pointing to ramdisk"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK"
|
||||
CURRENT_TARGET="$RAMDISK_PATH"
|
||||
notify "Transcode symlink was missing on $(hostname) — recreated" "Transcode Manager" "warning"
|
||||
fi
|
||||
elif [[ ! -e "$CURRENT_TARGET" ]]; then
|
||||
warn "$ICON_LINK Symlink target missing: $CURRENT_TARGET — resetting to ramdisk"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK"
|
||||
CURRENT_TARGET="$RAMDISK_PATH"
|
||||
notify "Transcode symlink target was missing on $(hostname) — reset to ramdisk" "Transcode Manager" "warning"
|
||||
fi
|
||||
|
||||
# Force recovery — point at ramdisk if mounted, SSD otherwise
|
||||
if [[ "$SYMLINK_OK" == false ]]; then
|
||||
if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
warn "Recovering symlink → ramdisk"
|
||||
set_symlink "$RAMDISK_PATH" force
|
||||
else
|
||||
warn "Ramdisk not mounted — recovering symlink → SSD"
|
||||
set_symlink "$TRANSCODE_SSD" force
|
||||
fi
|
||||
success "$ICON_LINK Symlink valid: $TRANSCODE_LINK → $CURRENT_TARGET"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# Ramdisk mount check
|
||||
# CHECK 3 — Ramdisk health
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
info "$ICON_RAM Checking ramdisk..."
|
||||
|
||||
RAMDISK_MOUNTED=true
|
||||
if ! mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
|
||||
warn "$ICON_RAM Ramdisk not mounted at $RAMDISK_PATH — falling back to SSD"
|
||||
RAMDISK_MOUNTED=false
|
||||
set_symlink "$TRANSCODE_SSD"
|
||||
notify "Ramdisk not found on $(hostname) — transcode symlink set to SSD fallback" "Transcode Manager" "warning"
|
||||
error "$ICON_RAM Ramdisk not mounted at $RAMDISK_PATH"
|
||||
RAMDISK_HEALTHY=false
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
warn "Flipping symlink to SSD — ramdisk unavailable"
|
||||
flip_symlink "$TRANSCODE_SSD" "ramdisk disappeared"
|
||||
CURRENT_TARGET="$TRANSCODE_SSD"
|
||||
notify "Ramdisk disappeared on $(hostname) — transcodes falling back to SSD. Run ramdisk_setup.sh to restore." "Transcode Manager" "warning"
|
||||
fi
|
||||
|
||||
if [[ "$RAMDISK_MOUNTED" == true ]]; then
|
||||
# Verify ramdisk size matches expected
|
||||
MOUNTED_SIZE=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $2}' | tr -d 'G')
|
||||
EXPECTED_SIZE=$(echo "$RAMDISK_SIZE" | tr -d 'G')
|
||||
if [[ "$MOUNTED_SIZE" != "$EXPECTED_SIZE" ]]; then
|
||||
warn "$ICON_RAM Ramdisk size mismatch — expected ${RAMDISK_SIZE} got ${MOUNTED_SIZE}G"
|
||||
else
|
||||
success "$ICON_RAM Ramdisk mounted and correct size (${RAMDISK_SIZE})"
|
||||
RAMDISK_SIZE_ACTUAL=$(df "$RAMDISK_PATH" --output=size -h | tail -1 | tr -d ' ')
|
||||
success "$ICON_RAM Ramdisk mounted — size: $RAMDISK_SIZE_ACTUAL"
|
||||
fix_permissions "$RAMDISK_PATH"
|
||||
fi
|
||||
|
||||
# Get current usage in KB — use raw bytes to avoid scientific notation issues
|
||||
RAMDISK_USED_KB=$(df "$RAMDISK_PATH" --output=used | tail -1 | tr -d ' ')
|
||||
RAMDISK_AVAIL_KB=$(df "$RAMDISK_PATH" --output=avail | tail -1 | tr -d ' ')
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# CHECK 4 — SSD health
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
info "$ICON_DISK Checking SSD fallback..."
|
||||
|
||||
# Convert to GB for display using awk — avoids bash integer/float issues
|
||||
RAMDISK_USED_GB=$(awk "BEGIN {printf \"%.2f\", $RAMDISK_USED_KB / 1048576}")
|
||||
RAMDISK_AVAIL_GB=$(awk "BEGIN {printf \"%.2f\", $RAMDISK_AVAIL_KB / 1048576}")
|
||||
if [[ ! -d "$TRANSCODE_SSD" ]]; then
|
||||
warn "$ICON_DISK SSD fallback path missing: $TRANSCODE_SSD"
|
||||
SSD_HEALTHY=false
|
||||
if [[ "$TRANSCODE_MANAGER_MODE" == "ssd" ]]; then
|
||||
error "Mode is 'ssd' but SSD path is missing — cannot continue"
|
||||
notify "Transcode SSD path missing on $(hostname) — mode is 'ssd', manual intervention needed" "Transcode Manager" "warning"
|
||||
exit 1
|
||||
else
|
||||
warn "SSD fallback disabled — will stay on ramdisk"
|
||||
fi
|
||||
else
|
||||
SSD_FREE_GB=$(get_ssd_free_gb)
|
||||
success "$ICON_DISK SSD available — ${SSD_FREE_GB}GB free"
|
||||
fix_permissions "$TRANSCODE_SSD"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# USAGE STATS
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
RAMDISK_USED_GB="0.00"
|
||||
RAMDISK_AVAIL_GB="0.00"
|
||||
|
||||
if [[ "$RAMDISK_HEALTHY" == true ]]; then
|
||||
RAMDISK_USED_GB=$(get_ramdisk_used_gb)
|
||||
RAMDISK_AVAIL_GB=$(get_ramdisk_avail_gb)
|
||||
RAMDISK_FILES=$(find "$RAMDISK_PATH" -type f 2>/dev/null | wc -l)
|
||||
RAMDISK_SIZE_H=$(du -sh "$RAMDISK_PATH" 2>/dev/null | cut -f1)
|
||||
fi
|
||||
|
||||
SSD_FILES=$(find "$TRANSCODE_SSD" -type f 2>/dev/null | wc -l)
|
||||
SSD_SIZE_H=$(du -sh "$TRANSCODE_SSD" 2>/dev/null | cut -f1)
|
||||
|
||||
info "$ICON_RAM Ramdisk usage: ${RAMDISK_USED_GB}GB used / ${RAMDISK_AVAIL_GB}GB available"
|
||||
info "$ICON_RAM Ramdisk files: ${RAMDISK_FILES:-0} ($RAMDISK_SIZE_H)"
|
||||
info "$ICON_DISK SSD files: $SSD_FILES ($SSD_SIZE_H)"
|
||||
|
||||
# File counts and sizes
|
||||
RAMDISK_FILES=$(find "$RAMDISK_PATH" -type f 2>/dev/null | wc -l)
|
||||
SSD_FILES=$(find "$TRANSCODE_SSD" -type f 2>/dev/null | wc -l)
|
||||
RAMDISK_SIZE_HUMAN=$(du -sh "$RAMDISK_PATH" 2>/dev/null | cut -f1)
|
||||
SSD_SIZE_HUMAN=$(du -sh "$TRANSCODE_SSD" 2>/dev/null | cut -f1)
|
||||
|
||||
info "$ICON_RAM Ramdisk files: $RAMDISK_FILES ($RAMDISK_SIZE_HUMAN)"
|
||||
info "$ICON_DISK SSD files: $SSD_FILES ($SSD_SIZE_HUMAN)"
|
||||
FLIP_COUNT=$(get_flip_count)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# Symlink direction decision — all comparisons via awk to handle float thresholds
|
||||
# MODE LOGIC
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
WARN_EXCEEDED=$(awk "BEGIN {print ($RAMDISK_USED_GB >= $RAMDISK_WARN_GB) ? 1 : 0}")
|
||||
LOW_RECOVERED=$(awk "BEGIN {print ($RAMDISK_USED_GB < $RAMDISK_LOW_GB) ? 1 : 0}")
|
||||
echo ""
|
||||
|
||||
if [[ "$WARN_EXCEEDED" == "1" ]]; then
|
||||
# Ramdisk is getting full — check SSD has enough space before flipping
|
||||
info "$ICON_DISK Checking SSD fallback space..."
|
||||
SSD_AVAIL_KB=$(df "$TRANSCODE_SSD" --output=avail | tail -1 | tr -d ' ')
|
||||
SSD_AVAIL_GB=$(awk "BEGIN {printf \"%.2f\", $SSD_AVAIL_KB / 1048576}")
|
||||
SSD_SUFFICIENT=$(awk "BEGIN {print ($SSD_AVAIL_GB >= $RAMDISK_SSD_MIN_GB) ? 1 : 0}")
|
||||
case "$TRANSCODE_MANAGER_MODE" in
|
||||
|
||||
if [[ "$SSD_SUFFICIENT" == "0" ]]; then
|
||||
error "$ICON_DISK SSD fallback only has ${SSD_AVAIL_GB}GB free — minimum is ${RAMDISK_SSD_MIN_GB}GB"
|
||||
notify "Transcode SSD fallback critically low on $(hostname) — ${SSD_AVAIL_GB}GB free" "Transcode Manager" "warning"
|
||||
ramdisk)
|
||||
info "$ICON_RAM Mode: RAMDISK — forcing symlink to ramdisk"
|
||||
if [[ "$RAMDISK_HEALTHY" == false ]]; then
|
||||
error "Ramdisk mode selected but ramdisk is not available"
|
||||
notify "Transcode ramdisk mode failed on $(hostname) — ramdisk not mounted" "Transcode Manager" "warning"
|
||||
else
|
||||
info "$ICON_DISK SSD has ${SSD_AVAIL_GB}GB free — flipping to SSD"
|
||||
set_symlink "$TRANSCODE_SSD"
|
||||
fi
|
||||
|
||||
elif [[ "$LOW_RECOVERED" == "1" ]]; then
|
||||
# Ramdisk has recovered — flip back
|
||||
set_symlink "$RAMDISK_PATH"
|
||||
|
||||
if [[ "$CURRENT_TARGET" != "$RAMDISK_PATH" ]]; then
|
||||
flip_symlink "$RAMDISK_PATH" "ramdisk mode"
|
||||
increment_flip_count > /dev/null
|
||||
else
|
||||
# In hysteresis gap — hold current state
|
||||
CURRENT=$(get_state "current_target")
|
||||
log "Usage ${RAMDISK_USED_GB}GB in hysteresis gap (${RAMDISK_LOW_GB}–${RAMDISK_WARN_GB}GB) — holding: $CURRENT"
|
||||
info "$ICON_RAM Usage in hysteresis gap — holding current symlink"
|
||||
log "Symlink already points to ramdisk — no change"
|
||||
fi
|
||||
if (( $(awk "BEGIN {print ($RAMDISK_USED_GB >= $RAMDISK_WARN_GB) ? 1 : 0}") )); then
|
||||
warn "$ICON_RAM Ramdisk usage ${RAMDISK_USED_GB}GB above threshold ${RAMDISK_WARN_GB}GB — consider switching to smart mode"
|
||||
notify "Ramdisk usage high on $(hostname) — ${RAMDISK_USED_GB}GB used in ramdisk-only mode" "Transcode Manager" "warning"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
ssd)
|
||||
info "$ICON_DISK Mode: SSD — forcing symlink to SSD"
|
||||
if [[ "$SSD_HEALTHY" == false ]]; then
|
||||
error "SSD mode selected but SSD path is not available"
|
||||
else
|
||||
if [[ "$CURRENT_TARGET" != "$TRANSCODE_SSD" ]]; then
|
||||
flip_symlink "$TRANSCODE_SSD" "ssd mode"
|
||||
increment_flip_count > /dev/null
|
||||
else
|
||||
log "Symlink already points to SSD — no change"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
smart)
|
||||
info "$ICON_GEAR Mode: SMART — auto threshold management"
|
||||
|
||||
if [[ "$EMBY_RUNNING" == false ]]; then
|
||||
info "Emby not running — skipping threshold checks"
|
||||
|
||||
elif [[ "$RAMDISK_HEALTHY" == false ]]; then
|
||||
info "Ramdisk unavailable — staying on SSD until ramdisk recovers"
|
||||
|
||||
elif [[ "$CURRENT_TARGET" == "$RAMDISK_PATH" ]]; then
|
||||
# On ramdisk — check if we need to flip to SSD
|
||||
if (( $(awk "BEGIN {print ($RAMDISK_USED_GB >= $RAMDISK_WARN_GB) ? 1 : 0}") )); then
|
||||
if [[ "$SSD_HEALTHY" == false ]]; then
|
||||
error "Ramdisk above threshold but SSD unavailable — cannot flip"
|
||||
notify "Transcode ramdisk full on $(hostname) and SSD unavailable — intervention needed" "Transcode Manager" "warning"
|
||||
else
|
||||
SSD_FREE_GB=$(get_ssd_free_gb)
|
||||
if (( $(awk "BEGIN {print ($SSD_FREE_GB < $RAMDISK_SSD_MIN_GB) ? 1 : 0}") )); then
|
||||
warn "$ICON_DISK SSD only ${SSD_FREE_GB}GB free — below ${RAMDISK_SSD_MIN_GB}GB minimum, not flipping"
|
||||
else
|
||||
warn "$ICON_RAM Ramdisk ${RAMDISK_USED_GB}GB — above ${RAMDISK_WARN_GB}GB, flipping to SSD"
|
||||
flip_symlink "$TRANSCODE_SSD" "threshold exceeded"
|
||||
CURRENT_TARGET="$TRANSCODE_SSD"
|
||||
NEW_COUNT=$(increment_flip_count)
|
||||
FLIP_COUNT=$NEW_COUNT
|
||||
if [[ "$NEW_COUNT" -ge "$TRANSCODE_FLIP_WARN" ]]; then
|
||||
notify "Transcode flipped to SSD on $(hostname) — ${NEW_COUNT} flips this hour. Consider increasing RAMDISK_SIZE." "Transcode Manager" "warning"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log "Symlink already points to ramdisk — no change"
|
||||
fi
|
||||
|
||||
elif [[ "$CURRENT_TARGET" == "$TRANSCODE_SSD" ]]; then
|
||||
# On SSD — check if we can flip back to ramdisk
|
||||
if (( $(awk "BEGIN {print ($RAMDISK_USED_GB <= $RAMDISK_LOW_GB) ? 1 : 0}") )); then
|
||||
info "$ICON_RAM Ramdisk ${RAMDISK_USED_GB}GB — below ${RAMDISK_LOW_GB}GB, flipping back to ramdisk"
|
||||
flip_symlink "$RAMDISK_PATH" "usage recovered"
|
||||
CURRENT_TARGET="$RAMDISK_PATH"
|
||||
NEW_COUNT=$(increment_flip_count)
|
||||
FLIP_COUNT=$NEW_COUNT
|
||||
else
|
||||
log "On SSD — ramdisk ${RAMDISK_USED_GB}GB still above low threshold ${RAMDISK_LOW_GB}GB"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
END=$(date +%s)
|
||||
CURRENT_TARGET=$(readlink "$TRANSCODE_LINK" 2>/dev/null || echo "unknown")
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
# ━━━ $ICON_SUMMARY Summary ━━━
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY TRANSCODE MANAGER SUMMARY ━━━━━"
|
||||
if [[ "$RAMDISK_MOUNTED" == true ]]; then
|
||||
echo "$ICON_GEAR Mode: $TRANSCODE_MANAGER_MODE"
|
||||
echo "$ICON_RAM Ramdisk: ${RAMDISK_USED_GB}GB used / ${RAMDISK_AVAIL_GB}GB available"
|
||||
else
|
||||
echo "$ICON_RAM Ramdisk: NOT MOUNTED — using SSD"
|
||||
fi
|
||||
echo "$ICON_LINK Symlink: $TRANSCODE_LINK → $(readlink "$TRANSCODE_LINK" 2>/dev/null || echo "BROKEN")"
|
||||
echo "$ICON_LINK Flips: $(get_state "flip_count_hour") this hour (warn at $TRANSCODE_FLIP_WARN)"
|
||||
echo "$ICON_LINK Symlink: $TRANSCODE_LINK → $CURRENT_TARGET"
|
||||
echo "$ICON_LINK Flips: $FLIP_COUNT this hour (warn at $TRANSCODE_FLIP_WARN)"
|
||||
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
|
||||
echo "$ICON_DONE Status: $ICON_SUCCESS DONE"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
Reference in New Issue
Block a user