diff --git a/docs/architecture.md b/docs/architecture.md index 0fb88a1..ff2014e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -423,3 +423,84 @@ mosaic + RTSP-server. После v1 cuframes: 3. После Phase 0 — review результатов, корректировка дизайна (если CUDA IPC повёл себя не как ожидали) 4. Phase 1+ по плану + +--- + +# Appendix A — Production deployment notes (post-v0.1.0) + +Реальные наблюдения после первого production deployment (Frigate + cctv-processor +на RTX 5090, 24h+ uptime). Обновляется по мере накопления опыта. + +## Что подтвердилось из изначального дизайна + +- **CUDA IPC handshake через cudaIpcEventHandle_t работает стабильно** — нет + ни одного torn frame за 24+ часов на 2 consumer'ах. +- **EXTERNAL ownership** (publisher передаёт свои pre-allocated CUDA pointers) + необходим для FFmpeg-based publisher — иначе нужен extra cudaMemcpy из FFmpeg's + hwframe pool в library-managed pool. +- **Unix socket handshake** ОК — простой, debug'абельный (`socat` для inspect). +- **POSIX shm для header + atomic seq counters** — race-free на reader side. + +## Что пришлось доработать в v0.1.0 vs initial design + +- **CMake install rules** изначально не предусмотрены. Downstream проекты + делали `cmake --install` → пустой prefix. Fix: `install(TARGETS ...)` + + `install(DIRECTORY include/cuframes ...)`. Лессон — install rules должны + быть в day 1. +- **Variable HINTS в find_library**: пользователи делают install в разные + prefix'ы. HINTS для downstream `find_library(cuframes)` должны включать + `$PREFIX/lib`, `$PREFIX/lib64`, и `build-dir/libcuframes/` для local-dev. + +## Что не учли в дизайне (открытые grабли — см. troubleshooting.md) + +### Cross-container CUDA IPC требует **shared pid + ipc namespace** + +`cudaIpcOpenEventHandle` validates IPC peer через `/proc//...`. Если +consumer container не в same PID namespace что publisher — fail с +`invalid device context`. + +Это **incompatible** с s6-overlay-based containers (linuxserver.io stack, +Frigate), требующими PID 1 для self. Workaround: только `ipc:` shared, +accept race window (works на Frigate в практике потому что подключается +первым после publisher restart). **Real fix planned v0.2**: socket-based +context validation вместо `/proc` reliance. + +### Publisher-side resize нужен для consumers без cuda-llvm + +Большинство downstream FFmpeg builds — без `--enable-cuda-llvm` (на платформах +с glibc < 2.38 эта опция не собирается, нужен `stdbit.h`). Без cuda-llvm нет +`scale_cuda` filter. Consumer вынужден CPU-resize либо отключать hwaccel. + +**Fix planned v0.2**: publisher принимает `--scale=WxH` и делает GPU resize +до publish. Consumer получает уже scaled frames, scale_cuda не нужен. + +### Encoded packet sharing — отсутствует в v0.1 + +cuframes v0.1 раздаёт **только decoded** NV12. Для `record` use case +(`-c:v copy` mux без decode) consumer всё ещё открывает свой RTSP — лимит +камеры на concurrent streams (4-5 у Dahua) hit'ится. + +**v0.2 spec**: parallel encoded-packets ring + `cuframes_packets://` +demuxer. См. [issue #2](https://git.goldix.org/gx/cuframes/issues/2). + +## Production setup (gold path) + +``` + ┌─► Frigate (FFmpeg cuframes:// demuxer) → detect +Camera RTSP ─► publisher ──┤ + (1× NVDEC) └─► cctv-processor (CuframesSource C++ API) → motion+RTSP-encode→TV +``` + +| Метрика | Without cuframes (baseline) | С cuframes v0.1 | +|---|---|---| +| NVDEC operations на parking-камеру | 2 (Frigate detect + cctv detect) | **1** (publisher) | +| VRAM extra cost | 0 (каждый своё) | ~3 MB (ring 6×460KB sub-stream) | +| RTSP camera load | 2 streams | **1** stream | +| Uptime (verified) | n/a | 24h+ без drops | + +## См. также + +- [docs/troubleshooting.md](troubleshooting.md) — конкретные грабли + fixes +- [BENCHMARKS.md](../BENCHMARKS.md) — измерения +- [docs/integrations/frigate.md](integrations/frigate.md) — guide для Frigate +- [ROADMAP.md](../ROADMAP.md) — v0.2/v0.3/v1.0 diff --git a/docs/requirements.md b/docs/requirements.md index c5df387..6c578e5 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -181,4 +181,36 @@ Phase 0 PoC (2026-05-14): - **Docker:** 29.1.3 с nvidia-container-runtime - **Container:** Ubuntu 24.04 + GCC 13 + Clang + CMake 3.28 + Ninja -Дополнительный target matrix будет в CI после Phase 4. +## Production deployment matrix (v0.1.0) + +Что подтверждено в 24h+ production run: + +| Слой | Версия | Comments | +|---|---|---| +| NVIDIA driver | 555+ | минимум для CUDA 12 user runtime | +| CUDA toolkit (build) | 12.4 (Debian 12 / Ubuntu 22.04) либо 13.0 (Ubuntu 24.04) | toolkit для builder image, не runtime | +| GPU | RTX 5090 (sm_120) | проверено; раньше — sm_75 минимум | +| Builder OS | Ubuntu 22.04 (glibc 2.35) | forward-compat с Debian 12 runtime | +| Runtime OS (Frigate) | Debian 12 (glibc 2.36) | base image Frigate `stable-tensorrt` | +| Runtime OS (cctv-backend) | Ubuntu 22.04 либо Debian 12 | matched с builder | +| Docker | 29.1.x | для buildx | +| docker buildx | v0.34.0+ | `apt install docker-buildx-plugin` либо manual install из GH releases | +| nvidia-container-toolkit | 1.14+ | для `runtime: nvidia` | + +## Docker namespace requirements (cross-container CUDA IPC) + +Для consumer'а который подключается к publisher'у в **другом** container'е: + +| Что нужно | Как настроить | +|---|---| +| `/dev/shm` shared (header + ring metadata) | `ipc: container:` либо `ipc: shareable` у publisher + same у consumer | +| `/proc` visibility (CUDA IPC peer validation) | `pid: container:` | +| `/run/cuframes/*.sock` доступен | volume mount `cuframes_sock:/run/cuframes:ro` | +| GPU access | `runtime: nvidia` + `NVIDIA_VISIBLE_DEVICES=all` | +| Socket file permissions | `user: root` либо chmod в publisher | + +**Все 5** должны быть выполнены. Подробности — [docs/troubleshooting.md](troubleshooting.md). + +**Special case: s6-overlay containers (Frigate, linuxserver.io stack)**: `pid:` share **невозможен** — s6-overlay требует PID 1. Workaround: только `ipc:` + race window connect. См. troubleshooting. + +Дополнительный target matrix будет в CI после Phase 4 (см. [ROADMAP.md](../ROADMAP.md)). diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..ea289bc --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,326 @@ +# Troubleshooting + +Реальные грабли которые мы прошли при первой production deployment'е cuframes +(Frigate + custom C++ processor + custom Python). Документировано чтобы вы их +не повторяли. + +## Содержание + +- [Runtime / CUDA IPC](#runtime--cuda-ipc) + - [`cudaIpcOpenEventHandle: invalid device context`](#cudaipcopeneventhandle-invalid-device-context) + - [Subscriber timeout (`cuframes_subscriber_create: timeout`)](#subscriber-timeout) + - [Permission denied на socket](#permission-denied-на-socket) +- [Frigate-specific](#frigate-specific) + - [`s6-overlay-suexec: fatal: can only run as pid 1`](#s6-overlay-suexec-fatal-can-only-run-as-pid-1) + - [`No such filter: 'scale_cuda'`](#no-such-filter-scale_cuda) + - [Missing dynamic .so после ffmpeg replace](#missing-dynamic-so-после-ffmpeg-replace) +- [Build / FFmpeg patch](#build--ffmpeg-patch) + - [`libcuframes not found` при configure](#libcuframes-not-found-при-configure) + - [`ffbuild/library.mak: No such file`](#ffbuildlibrarymak-no-such-file) + - [`could not find a working compiler` (GMP)](#could-not-find-a-working-compiler-gmp) + - [`zlib: download failed` в crosstool-NG](#zlib-download-failed-в-crosstool-ng) + - [`stdbit.h: No such file` при `--enable-cuda-llvm`](#stdbith-no-such-file-при---enable-cuda-llvm) +- [Docker / IPC](#docker--ipc) + - [Cross-container CUDA IPC: ipc + pid namespace share](#cross-container-cuda-ipc-ipc--pid-namespace-share) + - [Buildx container driver не видит host images](#buildx-container-driver-не-видит-host-images) +- [Networking / RTSP](#networking--rtsp) + - [RTSP/RTP UDP не доходит до клиента (docker NAT)](#rtsprtp-udp-не-доходит-до-клиента-docker-nat) + - [`Nonmatching transport in server reply`](#nonmatching-transport-in-server-reply) +- [Gitea Actions / CI](#gitea-actions--ci) + - [`node: executable file not found`](#node-executable-file-not-found) + - [`SyntaxError: Unexpected token '{'` (Node 12)](#syntaxerror-unexpected-token--node-12) + +--- + +## Runtime / CUDA IPC + +### `cudaIpcOpenEventHandle: invalid device context` + +**Симптом**: subscriber сразу после `cuframes_subscriber_create` падает с этой ошибкой. + +**Причина**: CUDA driver проверяет IPC peer через `/proc//...`. Если процесс publisher'а **не виден** в PID namespace consumer'а — context считается невалидным. + +**Fix**: shared PID namespace. + +Docker: +```yaml +consumer: + ipc: "container:" # shared /dev/shm + pid: "container:" # ← вот это критично, без него fail +``` + +Host process: запуск consumer'а на host'е (либо publisher'а на host'е тоже) — same default namespace. + +**Caveat**: если consumer image использует s6-overlay (Frigate, linuxserver.io +images) — `pid: container:` несовместим (см. [соответствующую секцию](#s6-overlay-suexec-fatal-can-only-run-as-pid-1)). + +### Subscriber timeout + +**Симптом**: `cuframes_subscriber_create: timeout` без других ошибок. + +**Причины** (в порядке вероятности): +1. `/run/cuframes/.sock` не виден consumer'у — забыли volume-mount +2. `/run/cuframes` смонтирован, но publisher ещё не успел создать socket — увеличить `connect_timeout_ms` +3. Publisher запущен, socket есть, но **permission denied** — см. ниже + +### Permission denied на socket + +**Симптом**: socket виден через `ls -la /run/cuframes/`, owner `root`. Consumer process — non-root user → не может `connect()`. + +**Fix**: +- Запустить consumer как root: `user: root` в compose +- Либо изменить permissions socket после создания (publisher delegation) — TBD в v0.2 + +--- + +## Frigate-specific + +### `s6-overlay-suexec: fatal: can only run as pid 1` + +**Симптом**: container Frigate'а в restart loop, в logs только эта ошибка. + +**Причина**: `pid: container:` сделал Frigate not-PID-1 в shared namespace. s6-overlay v3 strictly требует PID 1 для proper signal handling/zombie reaping. + +**Fix**: убрать `pid: container:` для Frigate. Только `ipc: container:` shared. + +**Trade-off**: без shared pid некоторые edge cases CUDA IPC ломаются (см. [соответствующую секцию](#cudaipcopeneventhandle-invalid-device-context)). Frigate **на практике** работает потому что подключается до того как CUDA driver проверяет peer (race window race), но если publisher restart'нётся посередине — Frigate'у не удастся пере-подключиться без перезапуска. + +**Real fix** (planned v0.2): encoded packet sharing — Frigate detect получает кадры через decoded path (work-around), record получает encoded через socket-based protocol который **не** требует cudaIpcOpenEventHandle. + +### `No such filter: 'scale_cuda'` + +**Симптом**: Frigate ffmpeg subprocess падает с этой ошибкой в `AVFilterGraph`. + +**Причина**: наш patched FFmpeg собран без `--enable-cuda-llvm` (см. [stdbit.h grабля](#stdbith-no-such-file-при---enable-cuda-llvm)). Без cuda-llvm в FFmpeg нет CUDA filters (scale_cuda, overlay_cuda). + +**Fix**: в Frigate config.yml явно отключи hwaccel cuda: +```yaml +ffmpeg: + hwaccel_args: [] # CPU scale вместо scale_cuda +``` + +Cost: 5-10% CPU per FHD25 камера. **Real fix** (v0.2): publisher-side resize в cuframes сам. + +### Missing dynamic .so после ffmpeg replace + +**Симптом**: после `docker cp` patched ffmpeg в Frigate container — `ldd ffmpeg` +показывает `libharfbuzz.so.0 => not found`, `libfribidi.so.0 => not found`, … +~20 missing .so. + +**Причина**: Frigate's bundled ffmpeg **статически слинкован** (NickM-27/FFmpeg-Builds +делает full static build). Все 30+ deps встроены в один binary. Frigate runtime +image **не имеет** этих .so packages installed (ему не надо — bundled ffmpeg +self-contained). + +Наш custom ffmpeg — **dynamic linked** (apt deps). Нужны .so на target. + +**Fix**: либо +- `apt install` missing libs в Frigate (additive image modification): + ```bash + apt install libharfbuzz0b libfribidi0 librist4 libsrt1.5-openssl libssh-4 \ + libvpx7 libwebpmux3 libwebp7 libdav1d6 libaom3 libmp3lame0 \ + libsvtav1enc1 libtheora0 libvorbis0a libvorbisenc2 \ + libx264-164 libx265-199 libopus0 + ``` +- Либо строить наш ffmpeg static (sources from NickM-27 pipeline) — complex + (см. [zlib download / GMP compiler граблю](#zlib-download-failed-в-crosstool-ng)) + +Best practice: создать `Dockerfile.frigate` overlay поверх Frigate image, +который добавляет deps и копирует ffmpeg. Запечь в image, не in-place patch. + +--- + +## Build / FFmpeg patch + +### `libcuframes not found` при configure + +**Симптом**: FFmpeg configure (с `--enable-libcuframes`) fails с этой ошибкой +из `enabled libcuframes && require libcuframes ...`. config.log показывает +`fatal error: cuframes/cuframes.h: No such file or directory`. + +**Причины**: + +1. **CMake install rules отсутствовали** в libcuframes (early commits до 601806a). + `cmake --install` создавал пустой prefix. Fix: обновить cuframes до ≥ 601806a. + +2. **Wrong HINTS в find_library**: твой проект ищет в `${CUFRAMES_ROOT}/build/...` + но install layout кладёт в `${CUFRAMES_ROOT}/lib`. Добавь оба пути в HINTS. + +3. **`rm -f libcuframes.so*`** удалил .so но **.a** file называется + `libcuframes_static.a` (не `libcuframes.a`) → linker не находит `-lcuframes`. + Fix: либо не удаляй .so, либо переименуй .a при install. + +### `ffbuild/library.mak: No such file` + +**Симптом**: configure FFmpeg success, но `make` падает сразу: +`Makefile:123: ffbuild/library.mak: No such file or directory`. + +**Причина**: вы сделали ваш fork FFmpeg через snapshot (не git clone), и **случайно +исключили `ffbuild/`** в rsync. Это **source files** FFmpeg, не build artifacts. + +**Fix**: убедись что `ffbuild/` есть в твоём FFmpeg checkout (`ls ffbuild/library.mak`). +Если делаешь snapshot через rsync — не используй `--exclude=ffbuild`. + +### `could not find a working compiler` (GMP) + +**Симптом**: crosstool-NG build падает на `Installing GMP for host` с +`configure: error: could not find a working compiler`. config.log показывает +`no, long long reliability test 1`. + +**Причина**: GMP 6.2.1 имеет known issue с GCC 11+ (Ubuntu 22.04 default). +Проверка long-long reliability fail'ит false-positive. + +**Fix**: pin GMP к 6.3.0 в `ct-ng-config`: +``` +CT_GMP_V_6_3=y +# CT_GMP_V_6_2 is not set +CT_GMP_VERSION="6.3.0" +``` + +И убедись что crosstool-NG version (commit) поддерживает 6.3.0 (≥ master 2024-09). + +### `zlib: download failed` в crosstool-NG + +**Симптом**: crosstool-NG step `Retrieving 'zlib-1.2.12'` fail'ит. + +**Причина**: zlib.net убрали старые versions с дефолтного location — теперь они +только в `/fossils/` subdirectory. Crosstool-NG hardcoded URL не работает. + +**Fix**: pre-fetch tarball + положить в local cache: +```bash +wget https://zlib.net/fossils/zlib-1.2.12.tar.gz -O preload/zlib-1.2.12.tar.gz +``` + +В Dockerfile перед `ct-ng build`: +```dockerfile +COPY preload/*.tar.gz /root/src/ +``` + +`CT_LOCAL_TARBALLS_DIR=${HOME}/src` — crosstool-NG найдёт в cache и не пойдёт +download. + +### `stdbit.h: No such file` при `--enable-cuda-llvm` + +**Симптом**: FFmpeg configure с `--enable-cuda-llvm` fail'ит: +`fatal error: stdbit.h: No such file or directory`. ERROR: cuda_llvm requested +but not found. + +**Причина**: `stdbit.h` — C23 standard header. Доступен в glibc ≥ 2.38. + +- Ubuntu 22.04 = glibc 2.35 — **нет** +- Debian 12 = glibc 2.36 — **нет** +- Ubuntu 24.04 = glibc 2.39 — есть +- Debian 13 (trixie) = glibc 2.38+ — есть + +**Fix options**: +1. Build на newer base (Ubuntu 24.04+). Но runtime target (Frigate Debian 12) + не запустит binary с glibc-2.38 symbols (backwards-incompatible). +2. Убрать `--enable-cuda-llvm`. Потеря: CUDA filters (`scale_cuda`, `overlay_cuda`, + `hwupload_cuda`). Decode/encode через NVDEC/NVENC всё равно работают. +3. Дождаться когда Frigate base обновится до newer Debian — вне твоего контроля. + +**На практике**: убираем cuda-llvm, в Frigate config `hwaccel_args: []`. +См. [scale_cuda секцию](#no-such-filter-scale_cuda). + +--- + +## Docker / IPC + +### Cross-container CUDA IPC: ipc + pid namespace share + +| Что нужно | Compose option | +|---|---| +| /dev/shm shared (для cuframes header + SHM ring) | `ipc: container:` (либо `ipc: shareable` у publisher + same у consumer) | +| /proc visibility (для CUDA IPC peer validation) | `pid: container:` | +| `/run/cuframes/*.sock` доступен | volume mount: `cuframes_sock:/run/cuframes:ro` | +| GPU access | `runtime: nvidia` | +| Socket permissions | `user: root` (либо chmod socket в publisher) | + +**Все 5** должны быть выполнены. Один пропуск — fail при subscriber_create или +cudaIpcOpenEventHandle. + +### Buildx container driver не видит host images + +**Симптом**: при использовании custom buildx builder (`docker buildx create +--driver docker-container ...`) с `FROM local-image:tag` — error `failed to +authorize: 403 Forbidden` (buildkit пытается pull с registry). + +**Причина**: container driver buildx изолирован, не имеет доступа к host's +local docker daemon images. Pull через registry. + +**Fix**: либо +- Не использовать custom builder — `docker buildx use default` (использует host + daemon). Минус: теряем `--cache-to/--cache-from type=local`. +- Либо push local image в **registry** (local или gitea), и buildx pull'ит оттуда. + +--- + +## Networking / RTSP + +### RTSP/RTP UDP не доходит до клиента (docker NAT) + +**Симптом**: RTSP server в docker контейнере с `ports: "554:8555"`. Клиент (TV, VLC) +делает RTSP SETUP successfully (TCP control работает), но video frames не приходят. + +**Причина**: RTP идёт **UDP**, sourced из docker network namespace. SNAT MASQUERADE +для outbound работает, но RTP destination port (которое клиент опубликовал в SETUP) +**не маппится обратно** через docker bridge — клиент видит UDP packets от чужого +source IP (docker network 172.x), не от 192.168.88.23 как expected. + +**Fix**: `network_mode: host` для RTSP-server контейнера. Тогда server listens +**напрямую** на host interfaces, RTP packets идут без NAT. + +Trade-offs: +- Все ports app'а listen на host network (нет port mapping). Проверь port collisions. +- DB env vars (postgres:5432 в docker network DNS) надо менять на host paths + (`localhost:5433` если postgres exposed на host port 5433). + +### `Nonmatching transport in server reply` + +**Симптом**: `ffprobe -rtsp_transport tcp -i rtsp://...` falls с этим сообщением. + +**Причина**: RTSP server возвращает SDP с UDP-only transport. Client ожидает TCP +interleaved. + +**Fix**: использовать UDP transport: `-rtsp_transport udp` (либо default behavior). +Если TV не поддерживает UDP — нужен RTSP server который умеет RTP-over-TCP +interleaved (cctv-processor v0.1 не умеет). + +--- + +## Gitea Actions / CI + +### `node: executable file not found` + +**Симптом**: первый JS action (например `actions/checkout@v4`) fail'ит: +`OCI runtime exec failed: exec: "node": executable file not found in $PATH`. + +**Причина**: гитея act_runner запускает JS actions через `node`, но твой +custom container (например `nvidia/cuda:...`) не имеет node installed. + +**Fix**: pre-install node в первом `run:` step (до actions/checkout): +```yaml +steps: + - name: Bootstrap node + run: apt-get update && apt-get install -y nodejs git ca-certificates + - name: Checkout + uses: actions/checkout@v4 +``` + +Либо использовать container с node pre-installed (`docker.gitea.com/runner-images:ubuntu-22.04`). + +### `SyntaxError: Unexpected token '{'` (Node 12) + +**Симптом**: после `apt install nodejs` в Ubuntu 22.04 — actions/checkout@v4 fail'ит: +`SyntaxError: Unexpected token '{' at static {...}`. + +**Причина**: Ubuntu 22.04 apt'овский `nodejs` = Node **12**. `actions/checkout@v4` +скомпилирован для Node 20+ (static class blocks — ES2022). + +**Fix**: install Node 20 from NodeSource: +```bash +curl -fsSL https://deb.nodesource.com/setup_20.x | bash - +apt-get install -y nodejs +``` + +В Ubuntu 24.04 apt уже даёт Node 20 — там goes автоматически.