Files
XC_VM/tools/test-install/STREAMTEST.md
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

20 KiB
Raw Blame History

Стрим-тест панели (e2e)

Сквозная проверка стриминга: локальный генератор отдаёт видеопоток → панель его рестримит → stream_check.py проверяет выход панели (каждый канал ~2 минуты) и пишет JSON-лог по каждому потоку.

Панель настраивается один раз вручную через админку, конфигурация сохраняется в дамп БД, а прогон теста восстанавливает дамп и запускает checker — повторяемо. Прямых SQL-запросов к БД нет: тест только снимает и восстанавливает бэкап (mysqldump / импорт).

генератор (127.0.0.1:8088)  ──►  панель (рестрим/LLOD)  ──►  get.php m3u  ──►  stream_check.py
   stream_server.py                 xc_fanout / monitor        выход панели         JSON-график

Команды test_release.sh

Команда Что делает
streamtest-gen поднять генератор в контейнере (для ручной настройки панели) + напечатать URL источников и админки
streamtest-gen-stop остановить генератор
streamtest-backup снять дамп настроенной БД в fixtures/streamtest-db.sql.gz
streamtest восстановить дамп → прогреть кэш → генератор → забрать m3u линии → прогнать checker (e2e)

Плюс отдельный инструмент tools/stream-check/stream_check.py с подкомандами check (checker), playlist (пакетный прогон) и graph (SVG-графики).

Требования

  • Контейнер установлен: ./tools/test-install/test_release.sh install
  • tools/test-stream-generator/sample.mp4 — короткий H.264/AAC .mp4 (в git его нет)
  • Внутри контейнера уже есть python3, встроенный ffmpeg, mariadb

0. Порты и доступ

  • Админка: http://localhost:8880/<код-доступа>/ — код обязателен, голый http://localhost:8880/ отдаёт 404. Код — в конце ./test_release.sh logs (строка вида http://…/PjmpGpxM/); streamtest-gen тоже печатает готовую ссылку.
  • Генератор: http://127.0.0.1:8088/ (внутри контейнера), проброшен на хост как http://localhost:8088/.

1. Завершить миграции БД

Свежая установка может показывать «Database Incomplete». Выполнить от root в контейнере:

docker exec xcvm-test-install /home/xc_vm/bin/php/bin/php /home/xc_vm/console.php status

Известный баг: миграция 010_add_shared_mount_prefixes.sql не идемпотентна — колонка уже есть в базовой схеме database.sql, поэтому на свежей установке она падает («Duplicate column») и статус остаётся «Incomplete». Если это мешает — эту миграцию нужно поправить (guard IF NOT EXISTS / убрать из базовой схемы) в src/migrations/010_add_shared_mount_prefixes.sql.

2. Запустить генератор (для ручной настройки)

./tools/test-install/test_release.sh streamtest-gen

Команда поднимает генератор в контейнере и печатает URL источников и готовую ссылку на админку (с кодом доступа):

  • MPEG-TS (LLOD): http://127.0.0.1:8088/stream.ts
  • HLS: http://127.0.0.1:8088/stream.m3u8

Оставьте его запущенным, пока настраиваете панель (остановить — ./test_release.sh streamtest-gen-stop).

3. Настроить панель вручную (админка)

Цель — чтобы оба потока реально пошли ONLINE (панель должна успешно рестримить источник от генератора). Порядок:

  1. Категория (Streams → Categories): создать live-категорию, напр. XC_VM Test Live.

  2. Поток 1 — MPEG-TS (Streams → Add):

    • Source: http://127.0.0.1:8088/stream.ts
    • Категория: созданная выше
    • Сервер: назначить Main Server как источник (узел «source» в дереве серверов — поток читает свой stream_source напрямую).
    • Режим запуска: on-demand (LLOD) либо always-on — на ваш выбор; для on-demand поток стартует при первом запросе checker'а.
  3. Поток 2 — HLS: то же, source http://127.0.0.1:8088/stream.m3u8.

  4. Проверить, что потоки ONLINE: в списке стримов у обоих должен появиться битрейт/статус «онлайн» (панель подключилась к генератору и рестримит). Это ключевая проверка — без неё выход панели будет отдавать заглушку «not on air».

  5. Bouquet (Bouquets → Add): создать бэкет, добавить оба потока.

  6. Линия (Lines → Add):

    • Username / Password: xcvmtest / xcvmtest (значения по умолчанию для теста; можно другие — тогда задайте STREAMTEST_USER / STREAMTEST_PASS).
    • Bouquet: выбрать созданный бэкет.
    • Allowed Output Formats: включить как минимум MPEG-TS.
    • Exp date — в будущем, Max connections ≥ 1, линия активна.
  7. Проверка выхода панели (в браузере или curl):

    http://localhost:8880/get.php?username=xcvmtest&password=xcvmtest&type=m3u_plus&output=ts
    

    Должен вернуться m3u со ссылками на выход панели (/play/<token>/ts), и они должны проигрываться (в плеере/ffprobe), а не отдавать заглушку.

4. Снять бэкап настроенной БД

Когда потоки идут ONLINE и выход панели проигрывается:

./tools/test-install/test_release.sh streamtest-backup

Дамп сохранится в tools/test-install/fixtures/streamtest-db.sql.gz (в git не коммитится — он привязан к текущей установке: креды, IP сервера, коды доступа).

5. Прогонять тест (повторяемо)

./tools/test-install/test_release.sh streamtest

Что делает: восстанавливает дамп БД → пересобирает кэши (cron:cache, cron:cache_engine) → поднимает генератор → забирает m3u линии с выхода панели → запускает stream_check.py playlist (по умолчанию 120 c на канал).

Результат:

  • tools/test-install/out/streamtest-<ts>.json — сводный отчёт (все каналы + summary);
  • tools/test-install/out/streamtest-<ts>/ — отдельный JSON-лог на каждый канал (<NN>-<имя>.json), пишется по мере прохождения каждого потока;
  • краткий pass/fail в консоль.

Код возврата 0 — все каналы healthy, 2 — есть проблемные.

6. Итерации

Если с первого раза потоки не проходят:

  1. ./test_release.sh streamtest — восстановит известное состояние БД из бэкапа (сбрасывает любые накопившиеся изменения) и прогонит checker.
  2. По JSON-логу видно, что не так (healthy=false, errors, счётчики очереди, samples — тайм-серия kbit/s и буфера).
  3. Поправьте настройку в админке (или запустите генератор streamtest-gen и донастройте), убедитесь, что потоки снова ONLINE.
  4. Снимите новый бэкап streamtest-backup (перезапишет фикстуру).
  5. Повторяйте streamtest, пока не будет зелёным.

Переменные окружения

Переменная По умолчанию Назначение
STREAMTEST_USER / STREAMTEST_PASS xcvmtest креды тест-линии (должны совпадать с настроенной)
STREAMTEST_DURATION 120 секунд на каждый канал
STREAMTEST_TOLERANCE 0 сколько транзиентных разрывов очереди (CC/sync) допускать; у on-demand рестрима на старте обычно 1–2 CC-разрыва — поставьте 2–5, чтобы их не считать за провал
STREAMTEST_STALL_TIMEOUT 15 секунд без новых данных = сталл — только для непрерывного TS. HLS этим не валится: длина HLS-сегментов часто намеренно рваная (рандомизация против DPI провайдера), поэтому HLS судится по реальному опустошению буфера (rebuffers), а не по паузе доставки
STREAMTEST_WARMUP 8 пауза после старта генератора до забора m3u
STREAMTEST_OUT_DIR /tmp/xcvm-streamlogs каталог per-stream логов в контейнере (хост копирует их в out/streamtest-<ts>/)
XCVM_STREAM_PORT 8088 порт генератора (хост→контейнер)

Пример: быстрый прогон по 20 с на канал — STREAMTEST_DURATION=20 ./test_release.sh streamtest.

Формат JSON-отчёта

{
  "generated_at": "2026-…Z",
  "duration_per_stream": 120,
  "streams": [
    {
      "name": "XC_VM Test TS", "url": "http://…/play/…/ts", "mode": "ts",
      "healthy": true,
      "details": { "kbit_s": 3372, "cc_errors": 0, "sync_errors": 0,
                   "hls_gaps": 0, "hls_disc": 0, "received_s": 120.0,
                   "rebuffers": 0, "max_data_gap_s": 1.2, "stalled": false },
      "samples": [ { "t": 0.0, "kbit_s": 0, "buffer_s": 0.0, "received_s": 0.0,
                     "state": "PREBUFFER", "cc_errors": 0, "sync_errors": 0,
                     "gaps": 0, "disc": 0 }, "… по одному в секунду …" ],
      "errors": []
    }
  ],
  "summary": { "total": 2, "healthy": 2, "failed": 0 }
}

samples — это «график» живого дашборда (stream_check.py check --live), сериализованный по секундам: пропускная способность, буфер, накопленные секунды и счётчики разрывов очереди (CC/sync для TS, gaps/disc для HLS).

Правило healthy: получены данные, queue_breaks ≤ tolerance, нет ошибок и — по режиму — TS: не было сталла (пауза доставки > stall-timeout); HLS: не было ребуферов (буфер не опустошался). Длина HLS-сегментов часто намеренно рваная (анти-DPI), поэтому одна лишь длинная пауза доставки HLS не валит — важно, реально ли опустел буфер.

Графики (SVG) для сравнения

stream_check.py graph рисует из JSON статические SVG-графики (чистый stdlib, без зависимостей) — их удобно сравнивать как картинки и вставлять в PR/доки.

На каждый поток — один SVG: пропускная способность (kbit/s) во времени с заливкой, кривая буфера на второй оси, амбровые полосы там, где виртуальный плеер не в состоянии PLAYING, красные штрихи разрывов очереди (CC/sync/gaps/disc), а в шапке — имя, mode, бейдж OK/FAIL и строка цифр (avg kbit/s · recv · cc/sync/gaps/disc · stall). Поток без сэмплов (упавший на открытии) тоже даёт картинку — с вердиктом и текстом ошибки.

Вход

Позиционные аргументы — любое сочетание файлов и каталогов (nargs="+"):

  • вся папка — сканируется по *.json (не рекурсивно);
  • отдельные файлы — по одному или списком/glob (logs/0*.json);
  • смесь папок и файлов в одной команде;

Один и тот же файл, попавший и через папку, и как отдельный аргумент, читается один раз (дедуп по realpath). Каждый JSON — либо per-stream запись (есть samples), либо сводный отчёт ("streams": [...], разворачивается в потоки).

# вся папка per-stream логов + сводный comparison.svg
python3 tools/stream-check/stream_check.py graph tools/test-install/out/streamtest-<ts>/ --combined --out-dir graphs/

# сводный отчёт одного прогона
python3 tools/stream-check/stream_check.py graph tools/test-install/out/streamtest-<ts>.json

# отдельные файлы / glob
python3 tools/stream-check/stream_check.py graph logs/01-MPEG-TS.json logs/02-*.json

# смесь: папка + отдельный файл (без дублей)
python3 tools/stream-check/stream_check.py graph logs/ other-run/07-HLS.json --combined

Опции

Опция По умолчанию Назначение
--out-dir DIR graphs каталог для SVG
--combined выкл. доп. comparison.svg — наложение битрейта всех потоков с легендой и вердиктами
--width PX 920 ширина графика
--height PX 460 высота графика

Результат — по graphs/<NN>-<имя>.svg на поток (+ comparison.svg при --combined). SVG открывается в браузере/IDE и при необходимости конвертируется в PNG (rsvg-convert, inkscape и т.п.).

Диагностика

  • Пустой m3u / панель отдаёт 404 на get.php — в восстановленном бэкапе нет линии с такими кредами (частый случай: линия названа не xcvmtest). Передайте реальное имя: STREAMTEST_USER=<line> STREAMTEST_PASS=<pass> ./test_release.sh streamtest. Также проверьте, что линия активна и бэкет не пуст (шаг 3.6–3.7).
  • healthy=false, stalled=true, ~100 КБ и тишина — панель отдаёт заглушку «not on air»: поток НЕ идёт ONLINE. Причины: поток не назначен на Main Server как источник; источник (генератор) недоступен; источник не стартовал. Убедитесь на шаге 3.4, что в админке у потоков есть битрейт.
  • HLS-поток FAIL с rebuffers > 0 — буфер реально опустел посреди воспроизведения. Длина HLS-сегментов намеренно рваная (анти-DPI), и checker это учитывает — HLS судится по rebuffers, а не по паузе доставки; так что rebuffers > 0 означает, что буфер слишком мелкий, чтобы поглотить длинный сегмент. Увеличьте буфер/окно на панели (client_prebuffer / request_prebuffer / seg_list_size) — это чаще тонкий буфер выбранного LLOD-режима, а не поломка нарезки. (stalled=true при rebuffers=0 у HLS — информативно, но за провал не считается.)
  • LLOD-поток не стартует, в логах панели «Undefined array key 0» в StreamProcess.php (startLLOD) — у этого потока пустой/невалидный stream_source. Впишите валидный источник в поле Source (шаг 3.2); панель обрабатывает llod 2 и 3 одинаково, «режим v3» тут ни при чём.
  • Генератор не запросили — в логе генератора (docker exec xcvm-test-install cat /tmp/xcvm-streamgen.log) нет GET /stream.ts: панель не пытается тянуть источник (см. предыдущий пункт).
  • Бэкап не подходит после переустановки — фикстура привязана к конкретной установке (креды/IP). После clean + install повторите ручную настройку и streamtest-backup.

Одиночная проверка (без панели)

stream_check.py работает и как одиночный чекер:

python3 tools/stream-check/stream_check.py check 'http://host/stream.ts' --duration 30 --json
python3 tools/stream-check/stream_check.py check 'http://host/stream.m3u8' --live         # ANSI-дашборд
python3 tools/stream-check/stream_check.py playlist 'path/or/url.m3u'                      # batch → сводный JSON в stdout
python3 tools/stream-check/stream_check.py playlist 'path/or/url.m3u' --out-dir logs/      # + отдельный файл на каждый стрим

--out-dir DIR (только для playlist) дополнительно пишет по одному файлу <NN>-<имя>.json на каждый поток — по мере его прохождения.

Сводный вывод зависит от того, куда идёт stdout: в интерактивном терминале — короткая сводка (без простыни JSON), при перенаправлении/пайпе (в файл, в streamtest) — полный сводный JSON. То есть … playlist url в терминале не засоряет консоль, а … playlist url > all.json даёт машинный JSON.

Берите URL в кавычки. Если в ссылке есть & (напр. .../playlist/TestLine/TestLine/m3u?output=hls&key=live), без кавычек bash посчитает & концом команды и уведёт её в фон, а key=live выполнит отдельно. Одинарные кавычки экранируют &, ?, = — весь URL уйдёт в playlist:

python3 tools/stream-check/stream_check.py playlist 'http://172.18.0.2:80/playlist/TestLine/TestLine/m3u?output=hls&key=live'

Через test_release.sh streamtest это не нужно — там URL уже передаётся в кавычках.