# Стрим-тест панели (e2e) Сквозная проверка стриминга: локальный **генератор** отдаёт видеопоток → **панель** его рестримит → `stream_check.py` проверяет **выход панели** (каждый канал ~2 минуты) и пишет JSON-лог по каждому потоку. Панель настраивается **один раз вручную** через админку, конфигурация сохраняется в **дамп БД**, а прогон теста восстанавливает дамп и запускает checker — повторяемо. Прямых SQL-запросов к БД нет: тест только снимает и восстанавливает бэкап (`mysqldump` / импорт). ```text генератор (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** в контейнере: ```bash 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. Запустить генератор (для ручной настройки) ```bash ./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//ts`), и они должны **проигрываться** (в плеере/ffprobe), а не отдавать заглушку. ## 4. Снять бэкап настроенной БД Когда потоки идут ONLINE и выход панели проигрывается: ```bash ./tools/test-install/test_release.sh streamtest-backup ``` Дамп сохранится в `tools/test-install/fixtures/streamtest-db.sql.gz` (в git не коммитится — он привязан к текущей установке: креды, IP сервера, коды доступа). ## 5. Прогонять тест (повторяемо) ```bash ./tools/test-install/test_release.sh streamtest ``` Что делает: восстанавливает дамп БД → пересобирает кэши (`cron:cache`, `cron:cache_engine`) → поднимает генератор → забирает m3u линии с выхода панели → запускает `stream_check.py playlist` (по умолчанию 120 c на канал). Результат: - `tools/test-install/out/streamtest-.json` — сводный отчёт (все каналы + summary); - `tools/test-install/out/streamtest-/` — **отдельный JSON-лог на каждый канал** (`-<имя>.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-/`) | | `XCVM_STREAM_PORT` | `8088` | порт генератора (хост→контейнер) | Пример: быстрый прогон по 20 с на канал — `STREAMTEST_DURATION=20 ./test_release.sh streamtest`. ## Формат JSON-отчёта ```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": [...]`, разворачивается в потоки). ```bash # вся папка per-stream логов + сводный comparison.svg python3 tools/stream-check/stream_check.py graph tools/test-install/out/streamtest-/ --combined --out-dir graphs/ # сводный отчёт одного прогона python3 tools/stream-check/stream_check.py graph tools/test-install/out/streamtest-.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/-<имя>.svg` на поток (+ `comparison.svg` при `--combined`). SVG открывается в браузере/IDE и при необходимости конвертируется в PNG (`rsvg-convert`, `inkscape` и т.п.). ## Диагностика - **Пустой m3u / панель отдаёт 404 на `get.php`** — в восстановленном бэкапе нет линии с такими кредами (частый случай: линия названа не `xcvmtest`). Передайте реальное имя: `STREAMTEST_USER= STREAMTEST_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` работает и как одиночный чекер: ```bash 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`) дополнительно пишет по одному файлу `-<имя>.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`: > > ```bash > 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 уже передаётся в кавычках.