Files
XC_VM/tools/test-install/streamtest_in_container.sh
T
Divarion_D 175198a902 refactor(stream-check): merge checker and grapher into one stream_check.py
Consolidate the two stream-check tools into a single master script with
three subcommands:

- check <url>          verify one stream (or --live dashboard)
- playlist <path|url>  batch an .m3u list -> aggregate JSON (+ per-stream files)
- graph <inputs...>    render the JSON as SVG charts

Shared helpers (HTTP, slugify, m3u/JSON handling) are now defined once.
The old --playlist flag becomes the `playlist` subcommand and the bare-URL
form becomes `check <url>`; the streamtest harness is updated accordingly.

Also fix the per-stream SVG rendering black in viewers that do not support
8-digit #rrggbbaa hex: the bitrate area fill and not-PLAYING bands now use
6-digit hex plus a separate fill-opacity attribute.

Docs (English + tools READMEs + STREAMTEST) updated to the new invocation.
Removes stream_queue_check.py and stream_graph.py.
2026-09-06 11:11:10 +03:00

141 lines
5.6 KiB
Bash

#!/bin/bash
# streamtest_in_container.sh — runs INSIDE the xcvm test container.
#
# Assumes the panel has ALREADY been configured (manually, once) and that
# configuration has been captured with `test_release.sh streamtest-backup` and
# restored by the host wrapper before this script runs. See STREAMTEST.md.
#
# This script does NOT touch the database. It only:
# 1. rebuilds the file caches the streaming path reads (cron:cache, cron:cache_engine)
# 2. launches tools/test-stream-generator as the upstream source (bundled ffmpeg)
# 3. fetches the panel's OUTPUT m3u for the configured test line
# 4. runs stream_check.py playlist against the panel output
#
# JSON report → STDOUT (captured by the host wrapper); progress → STDERR.
# Exit code = the checker's (0 = all healthy, 2 = a stream failed, 3 = setup error).
set -uo pipefail
log() { echo "[streamtest] $*" >&2; }
TOOLS=/opt/xcvm-tools
XC=/home/xc_vm
PHP="$XC/bin/php/bin/php"
CONSOLE="$XC/console.php"
USER_NAME="${STREAMTEST_USER:-xcvmtest}"
USER_PASS="${STREAMTEST_PASS:-xcvmtest}"
DURATION="${STREAMTEST_DURATION:-120}"
TOLERANCE="${STREAMTEST_TOLERANCE:-0}"
STALL_TIMEOUT="${STREAMTEST_STALL_TIMEOUT:-15}"
GEN_PORT="${STREAMTEST_GEN_PORT:-8088}"
OUT_DIR="${STREAMTEST_OUT_DIR:-/tmp/xcvm-streamlogs}" # per-stream JSON logs (copied out by the host)
GEN="http://127.0.0.1:$GEN_PORT"
# ── HTTP GET via python3 (always present; avoids a curl/wget dependency) ──
http_get() { # http_get <url> <out-file> → 0 on success
python3 - "$1" "$2" <<'PY'
import sys, urllib.request
url, out = sys.argv[1], sys.argv[2]
try:
data = urllib.request.urlopen(url, timeout=10).read()
except Exception as e:
sys.stderr.write("fetch fail: %s\n" % e)
sys.exit(1)
with open(out, "wb") as fh:
fh.write(data)
PY
}
as_xcvm() { # run a command as the xc_vm service user when possible
if command -v sudo >/dev/null 2>&1 && id xc_vm >/dev/null 2>&1; then
sudo -u xc_vm "$@"
else
"$@"
fi
}
# ── locate bundled ffmpeg (only present after the panel is installed) ──
FFMPEG=""
for v in 8.0 7.1 4.0; do
if [[ -x "$XC/bin/ffmpeg_bin/$v/ffmpeg" ]]; then
FFMPEG="$XC/bin/ffmpeg_bin/$v/ffmpeg"
break
fi
done
[[ -n "$FFMPEG" ]] || { log "ERROR: no bundled ffmpeg under $XC/bin/ffmpeg_bin (is the panel installed?)"; exit 3; }
log "ffmpeg: $FFMPEG"
SAMPLE="$TOOLS/test-stream-generator/sample.mp4"
[[ -f "$SAMPLE" ]] || { log "ERROR: sample.mp4 not found at $SAMPLE (is tools/ mounted?)"; exit 3; }
# ── rebuild the file caches from the (restored) DB ──
log "cache: cron:cache"
as_xcvm "$PHP" "$CONSOLE" cron:cache >&2 2>&1 || log "warn: cron:cache rc=$?"
log "cache: cron:cache_engine"
as_xcvm "$PHP" "$CONSOLE" cron:cache_engine >&2 2>&1 || log "warn: cron:cache_engine rc=$?"
# ── launch the upstream generator (background; killed on exit) ──
# Kill any prior generator and WAIT for the port to actually free before binding
# (a just-terminated process can hold the socket briefly → "Address already in use").
pkill -f 'test-stream-generator/stream_server.py' 2>/dev/null || true
for _ in $(seq 1 12); do
python3 -c "import socket,sys
s=socket.socket()
try:
s.bind(('0.0.0.0', $GEN_PORT)); s.close()
except OSError:
sys.exit(1)" 2>/dev/null && break
sleep 0.5
done
log "generator: starting on :$GEN_PORT (advertise 127.0.0.1)"
nohup python3 "$TOOLS/test-stream-generator/stream_server.py" \
-i "$SAMPLE" --port "$GEN_PORT" --advertise-host 127.0.0.1 --ffmpeg "$FFMPEG" \
>/tmp/xcvm-streamgen.log 2>&1 &
GEN_PID=$!
trap 'kill "$GEN_PID" 2>/dev/null; pkill -f stream_server.py 2>/dev/null' EXIT
ready=0
for _ in $(seq 1 15); do
if http_get "$GEN/playlist.m3u" /tmp/xcvm-gen-check.m3u 2>/dev/null; then ready=1; break; fi
sleep 1
done
[[ $ready -eq 1 ]] || { log "ERROR: generator not ready"; sed 's/^/[gen] /' /tmp/xcvm-streamgen.log >&2; exit 3; }
log "generator: ready"
# ── give on-demand/monitored streams a moment to warm (cron:streams starts them) ──
sleep "${STREAMTEST_WARMUP:-8}"
# ── fetch the panel's OUTPUT m3u for the configured test line ──
PL=/tmp/xcvm-panel.m3u
fetched=0
for url in \
"http://127.0.0.1/get.php?username=$USER_NAME&password=$USER_PASS&type=m3u_plus&output=ts" \
"http://127.0.0.1/api/playlist?username=$USER_NAME&password=$USER_PASS&type=m3u_plus&output=ts" \
"http://127.0.0.1/playlist/$USER_NAME/$USER_PASS/m3u_plus"; do
if http_get "$url" "$PL" 2>/dev/null && head -1 "$PL" 2>/dev/null | grep -q '#EXTM3U'; then
log "playlist: fetched via ${url%%\?*}"
fetched=1
break
fi
done
if [[ $fetched -ne 1 ]]; then
log "ERROR: could not fetch a valid panel playlist for line '$USER_NAME'."
log "The restored backup must contain a line with THESE credentials and a non-empty bouquet."
log "If your line uses a different name, pass it explicitly, e.g.:"
log " STREAMTEST_USER=<line> STREAMTEST_PASS=<pass> ./test_release.sh streamtest"
log "See STREAMTEST.md."
log "last response head:"; head -5 "$PL" 2>/dev/null | sed 's/^/[body] /' >&2
exit 3
fi
log "panel playlist:"; sed 's/^/[streamtest] /' "$PL" >&2
# ── run the checker against the panel output (aggregate JSON → stdout,
# per-stream JSON logs → $OUT_DIR, which the host copies out) ──
rm -rf "$OUT_DIR" 2>/dev/null; mkdir -p "$OUT_DIR"
log "checker: testing each stream for ${DURATION}s (tolerance=${TOLERANCE}, stall-timeout=${STALL_TIMEOUT}s); per-stream logs → $OUT_DIR"
python3 "$TOOLS/stream-check/stream_check.py" playlist "$PL" \
--duration "$DURATION" --tolerance "$TOLERANCE" \
--stall-timeout "$STALL_TIMEOUT" --out-dir "$OUT_DIR"
rc=$?
log "checker: exit $rc"
exit $rc