2026-08-23 13:49:37 +03:00
# Стрим-тест панели (e2e)
Сквозная проверка стриминга: локальный **генератор** отдаёт видеопоток →
2026-09-06 11:11:10 +03:00
**панель** его рестримит → `stream_check.py` проверяет **выход панели**
2026-08-23 13:49:37 +03:00
(каждый канал ~2 минуты) и пишет JSON-лог по каждому потоку.
Панель настраивается **один раз вручную** через админку, конфигурация
сохраняется в **дамп БД** , а прогон теста восстанавливает дамп и запускает
checker — повторяемо. Прямых SQL-запросов к БД нет: тест только снимает и
восстанавливает бэкап (`mysqldump` / импорт).
```text
2026-09-06 11:11:10 +03:00
генератор (127.0.0.1:8088) ──► панель (рестрим/LLOD) ──► get.php m3u ──► stream_check.py
2026-08-23 13:49:37 +03:00
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) |
2026-09-06 11:11:10 +03:00
Плюс отдельный инструмент `tools/stream-check/stream_check.py` с подкомандами
`check` (checker), `playlist` (пакетный прогон) и `graph` (SVG-графики).
2026-08-23 13:49:37 +03:00
## Требования
- Контейнер установлен: `./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/<token>/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 линии с выхода панели →
2026-09-06 11:11:10 +03:00
запускает ` stream_check.py playlist` (по умолчанию 120 c на канал).
2026-08-23 13:49:37 +03:00
Результат:
- ` 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-отчёта
` ``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 }
}
` ``
2026-09-06 11:11:10 +03:00
` samples` — это «график» живого дашборда (` stream_check.py check --live`),
2026-08-23 13:49:37 +03:00
сериализованный по секундам: пропускная способность, буфер, накопленные секунды
и счётчики разрывов очереди (CC/sync для TS, gaps/disc для HLS).
**Правило ` healthy`:** получены данные, ` queue_breaks ≤ tolerance`, нет ошибок и —
по режиму — **TS**: не было сталла (пауза доставки > ` stall-timeout`); **HLS**: не
было ребуферов (буфер не опустошался). Длина HLS-сегментов часто намеренно рваная
(анти-DPI), поэтому одна лишь длинная пауза доставки HLS не валит — важно, реально
ли опустел буфер.
## Графики (SVG) для сравнения
2026-09-06 11:11:10 +03:00
` stream_check.py graph` рисует из JSON статические **SVG-графики** (чистый
2026-08-23 13:49:37 +03:00
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
2026-09-06 11:11:10 +03:00
python3 tools/stream-check/stream_check.py graph tools/test-install/out/streamtest-<ts>/ --combined --out-dir graphs/
2026-08-23 13:49:37 +03:00
# сводный отчёт одного прогона
2026-09-06 11:11:10 +03:00
python3 tools/stream-check/stream_check.py graph tools/test-install/out/streamtest-<ts>.json
2026-08-23 13:49:37 +03:00
# отдельные файлы / glob
2026-09-06 11:11:10 +03:00
python3 tools/stream-check/stream_check.py graph logs/01-MPEG-TS.json logs/02-*.json
2026-08-23 13:49:37 +03:00
# смесь: папка + отдельный файл (без дублей)
2026-09-06 11:11:10 +03:00
python3 tools/stream-check/stream_check.py graph logs/ other-run/07-HLS.json --combined
2026-08-23 13:49:37 +03:00
` ``
### Опции
| Опция | По умолчанию | Назначение |
|---|---|---|
| ` --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`.
## Одиночная проверка (без панели)
2026-09-06 11:11:10 +03:00
` stream_check.py` работает и как одиночный чекер:
2026-08-23 13:49:37 +03:00
` ``bash
2026-09-06 11:11:10 +03:00
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/ # + отдельный файл на каждый стрим
2026-08-23 13:49:37 +03:00
` ``
2026-09-06 11:11:10 +03:00
` --out-dir DIR` (только для ` playlist`) дополнительно пишет по одному файлу
2026-08-23 13:49:37 +03:00
` <NN>-<имя>.json` на каждый поток — по мере его прохождения.
Сводный вывод зависит от того, куда идёт stdout: в **интерактивном терминале** —
короткая сводка (без простыни JSON), при **перенаправлении/пайпе** (в файл, в
2026-09-06 11:11:10 +03:00
` streamtest`) — полный сводный JSON. То есть ` … playlist url` в терминале не
засоряет консоль, а ` … playlist url > all.json` даёт машинный JSON.
2026-08-23 13:49:37 +03:00
> **Берите URL в кавычки.** Если в ссылке есть ` &` (напр.
> ` .../playlist/TestLine/TestLine/m3u?output=hls&key=live`), без кавычек bash
> посчитает ` &` концом команды и уведёт её в фон, а ` key=live` выполнит отдельно.
2026-09-06 11:11:10 +03:00
> Одинарные кавычки экранируют ` &`, ` ?`, ` =` — весь URL уйдёт в ` playlist`:
2026-08-23 13:49:37 +03:00
>
> ` ``bash
2026-09-06 11:11:10 +03:00
> python3 tools/stream-check/stream_check.py playlist 'http://172.18.0.2:80/playlist/TestLine/TestLine/m3u?output=hls&key=live'
2026-08-23 13:49:37 +03:00
> ` ``
>
> Через ` test_release.sh streamtest` это не нужно — там URL уже передаётся в кавычках.