Мониторинг и метрики
Что показывает панель, метрики Prometheus на закрытом эндпоинте /metrics, поток событий SSE, журнал и показатели, за которыми стоит следить.
У сервера четыре независимых источника наблюдаемости, и выбирать между ними нужно осознанно:
| Источник | Формат | Для чего |
|---|---|---|
| Панель управления | веб-интерфейс | глазами, здесь и сейчас |
GET /metrics |
текст Prometheus | внешняя система сбора метрик и оповещений |
GET /api/v1/events |
Server-Sent Events | живая лента изменений для своих панелей |
GET /api/v1/logs |
JSON | разбор конкретного инцидента |
Все четыре читают одно и то же состояние; ничего не считается дважды.
Что показывает панель#
Раздел Overview — сводка узла. Шесть плиток вверху:
| Плитка | Значение | Норма |
|---|---|---|
Live streams |
сколько потоков сейчас принимается | равно числу настроенных, если ни один не выключен |
Live viewers |
зрителей живого видео | — |
DVR viewers |
зрителей архива | — |
All sessions |
всего сессий отдачи | сумма двух предыдущих |
Ingress |
входящая полоса от источников | пропорциональна числу и битрейту потоков |
Egress |
исходящая полоса к зрителям | ≈ Ingress × число зрителей на поток |
Ниже — панель Resource posture с четырьмя показателями: Process CPU (в ядрах), Host CPU,
Process memory (RSS) и Host memory.
Раздел System разворачивает то же самое подробнее: загрузка по каждому ядру, аптайм, открытые
дескрипторы, потери приёма UDP и тренды процессора и памяти за две минуты.
Индикатор в верхней панели говорит, откуда взяты цифры: Live telemetry / SSE live — данные идут
потоком событий; Polling — поток недоступен, идёт периодический опрос; Data stale · 7s — показаны
последние полученные данные и им столько-то секунд.

Колокольчик Alerts в верхней панели копит оповещения об ухудшении здоровья потоков. Оповещение
приходит на переходе состояния — при деградации, при отказе, при появлении сразу нездорового
потока и при восстановлении. Залпа оповещений при старте сервера не бывает.
Метрики Prometheus#
GET http://media.example.com:8080/metrics
Формат — text/plain; version=0.0.4. Обратите внимание: это корень хоста, а не
/api/v1/metrics. Второй адрес тоже существует, но отдаёт JSON-дамп и служит другой задаче — см.
GET /api/v1/metrics.
Эндпоинт закрыт и требует токена#
/metrics отвечает 401 — всегдаЭндпоинт не публичный. Он принимает те же учётные данные, что и /api/v1, и без них отвечает
401 {"error":"unauthorized"} — в том числе на сервере, где вообще не настроено ни одних учётных
данных. Иначе и быть не может: тело экспозиции называет каждый поток, его здоровье и счётчики
зрителей, то есть раскрывает ровно то, ради чего закрыт /api/v1.
Значит, сборщику метрик нужно выдать токен. Токен передаётся строго заголовком Authorization;
строкой запроса — никогда.
# Без токена: curl -sS -o /dev/null -w '%{http_code}\n' "$BASE/metrics"
401
# С токеном: curl -sS "$BASE/metrics" -H "Authorization: Bearer $TOKEN" | head -3
# HELP hastreamer_process_cpu_cores Process CPU usage in cores (cputime-delta / cores). # TYPE hastreamer_process_cpu_cores gauge hastreamer_process_cpu_cores 0.1000
Токен для сборщика#
Выдавать системе мониторинга пароль администратора не нужно и не следует: сессия живёт 12 часов, а сборщик работает годами. Заведите статический токен чтения — учётную запись, которая ничего не может изменить.
auth { admin_password "пароль-администратора"; api_read_token "не-короче-16-символов"; }
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
api_read_token |
строка, не короче 16 символов | не задан | горячее |
Этот токен открывает только безопасные методы (GET, HEAD, OPTIONS) на /api/v1/* и
GET /metrics. Любая попытка что-либо изменить получает
403 {"error":"forbidden","reason":"read_token_cannot_write"}, а чтение конфигурации целиком —
403 … "reason":"read_token_cannot_read_config". Подробнее — Введение и аутентификация.
Настройка сборщика — обычная авторизация bearer:
scrape_configs: - job_name: sirinvideo metrics_path: /metrics scheme: https authorization: type: Bearer credentials: "не-короче-16-символов" static_configs: - targets: ["media.example.com:8080"]
Что экспортируется#
Процесс и хост
| Серия | Тип | Смысл |
|---|---|---|
hastreamer_process_cpu_cores |
gauge | потребление CPU процессом в ядрах |
hastreamer_worker_cores |
gauge | сколько ядер отведено под медиа |
hastreamer_process_rss_bytes |
gauge | резидентная память процесса |
hastreamer_host_cpu_busy_ratio |
gauge | занятость процессора хоста, доля 0…1 |
hastreamer_host_cpus |
gauge | логических ядер на хосте |
hastreamer_mem_total_bytes |
gauge | всего памяти на хосте |
hastreamer_mem_available_bytes |
gauge | доступно памяти на хосте |
hastreamer_open_fds |
gauge | открытых файловых дескрипторов |
hastreamer_uptime_seconds |
gauge | время работы процесса |
Нагрузка
| Серия | Тип | Смысл |
|---|---|---|
hastreamer_streams |
gauge | работающих потоков на узле |
hastreamer_sessions |
gauge | активных сессий отдачи |
Приём по UDP
| Серия | Тип | Смысл |
|---|---|---|
hastreamer_udp_ingest_kernel_drops_total |
counter | датаграмм отброшено буфером сокета ядра |
hastreamer_udp_ingest_queue_dropped_total |
counter | датаграмм отброшено очередью приёма |
hastreamer_udp_ingest_recv_errors_total |
counter | ошибок чтения сокета |
По потокам (метка stream)
| Серия | Тип | Смысл |
|---|---|---|
hastreamer_stream_no_data_seconds{stream} |
gauge | секунд с момента последних данных от источника |
hastreamer_stream_ingest_latency_ms{stream} |
gauge | задержка приёма, миллисекунды |
hastreamer_streams_omitted |
gauge | сколько потоков не попало в экспозицию из-за ограничения |
Серии с меткой stream выгружаются не более чем для 500 потоков. Если потоков больше, лишние
молча не попадают в экспозицию, а их число видно в hastreamer_streams_omitted. Проверяйте эту серию
на больших установках: ненулевое значение означает, что часть потоков вы не видите.
Готовые правила оповещения#
groups: - name: sirinvideo rules: - alert: StreamSourceSilent expr: hastreamer_stream_no_data_seconds > 30 for: 1m annotations: summary: "Поток {{ $labels.stream }} не получает данных 30+ секунд" - alert: UdpIngestDrops expr: rate(hastreamer_udp_ingest_kernel_drops_total[5m]) > 0 for: 5m annotations: summary: "Ядро отбрасывает датаграммы приёма — мал буфер сокета" - alert: StreamMetricsTruncated expr: hastreamer_streams_omitted > 0 annotations: summary: "Метрики по потокам обрезаны ограничением на 500 рядов" - alert: WorkerCoresNearLimit expr: hastreamer_process_cpu_cores / hastreamer_worker_cores > 0.85 for: 10m annotations: summary: "Занято более 85% отведённых ядер"
Поток событий (SSE)#
GET /api/v1/events?topics=<темы через запятую>
Единственный живой канал: text/event-stream, без буферизации на прокси. Пока подписчиков нет,
сервер не выполняет для него никакой работы.
Авторизация. Обычный заголовок Authorization: Bearer, и только этот маршрут дополнительно
принимает тот же токен в параметре ?token= — потому что браузерный EventSource не умеет задавать
заголовки. Строкой запроса принимается лишь сессионный токен; статические токены — никогда.
curl -sN "$BASE/api/v1/events?topics=totals,alerts" -H "Authorization: Bearer $TOKEN"
id: 2614
data: {"payload":{"bytes_in":6735178851,"bytes_out":117270589,"dvr_bytes_out":24412877,
"dvr_sessions":0,"live_bytes_out":92857712,"live_sessions":1,"sessions":1,"streams":10},
"seq":2614,"topic":"totals","ts":1787143322623}
: hb
Формат кадра. Строки event: не бывает никогда — всё едет обычным сообщением SSE, а клиент
маршрутизирует по полю topic внутри конверта:
{"topic":"…","seq":123,"ts":1787092182193,"payload":{…}}
Не реже чем раз в 10 секунд приходит сердцебиение : hb — даже если все темы отфильтрованы.
Темы#
| Тема | Когда приходит | Полезная нагрузка |
|---|---|---|
totals |
каждую секунду | те же поля, что у GET /totals |
sessions |
каждую секунду | sessions, dvr_sessions, live_sessions, joined, left |
alerts |
только на переходе здоровья | id, kind, severity, title, detail, stream, core, ts, from, to, reasons, alive, viewers |
sessions-audit |
на открытии и закрытии сессии | событие open / close / checkpoint с ключом сессии, протоколом и адресом |
stream:<id> |
каждую секунду по каждому подписанному потоку | alive, health, health_reasons, viewers, dvr_viewers, fps, bitrate, packets_in_per_s, source_error, reconnects |
Отсутствие параметра topics означает все темы. Тема logs принимается в списке, но пока
молчит.
Значения alerts.kind: degraded, failing, absent, recovered, removed. Значения
severity: info для восстановления, warn для деградации, error для остального.
# Следить за здоровьем одного потока curl -sN "$BASE/api/v1/events?topics=stream:cam1,alerts" -H "Authorization: Bearer $TOKEN"
Если заголовок Last-Event-ID попадает в окно кольца (512 кадров), сервер дословно повторит
пропущенные кадры. Если не попадает или подключение новое — сразу присылает снимок: один кадр
totals, один sessions и по одному alerts на каждый нездоровый поток. Кадры stream:<id> не
несут идентификатора и потому никогда не переигрываются.
sessions-audit — единственный способ увидеть каждое подключениеТема отдаёт события открытия и закрытия отдельных сессий, но только пока подписчик подключён: за прошедшее время она ничего не покажет. Если нужен учёт всех просмотров, держите подписчика постоянно и складывайте события в своё хранилище.
Журнал#
Операционный журнал живёт в кольцевом буфере в памяти процесса (до 4096 записей) и доступен в разделе
Logs панели и через GET /api/v1/logs.
curl -sS "$BASE/api/v1/logs?level=warn&limit=5" -H "Authorization: Bearer $TOKEN"
{"capture_level":"info","head_seq":17, "entries":[{"seq":17,"ts_ms":1787143225999,"level":"warn","target":"source", "stream":"cam1","msg":"[cam1] source stopped: error: Timeout"}]}
Порог записи задаётся директивой log_level:
| Параметр | Тип и допустимые значения | По умолчанию | Применение |
|---|---|---|---|
log_level |
debug | info | warn | error |
info |
горячее |
Записи ниже capture_level в кольцо никогда не попадали. Запрос ?level=debug на сервере с
порогом info вернёт пусто, и это не ошибка. Поднимите подробность (директива применяется сразу) и
дождитесь новых событий; уже накопленное кольцо не пересобирается.
Для постоянного хранения журнал нужно снимать наружу: сервер пишет структурированный вывод в стандартный поток, который забирает журнал системы инициализации.
journalctl -u hastreamer -f
За чем следить#
Порядок — по частоте, с которой эти показатели действительно спасают.
| Показатель | Где смотреть | Тревожно, когда |
|---|---|---|
| Секунды без данных от источника | hastreamer_stream_no_data_seconds, колонка Health |
больше 30 с подряд: камера отвалилась или закрыт порт |
| Здоровье потоков | вкладки Degraded и Failing, тема alerts |
ненулевое число дольше нескольких минут |
| Переподключения источника | reconnects в карточке потока, вкладка Source |
растёт постоянно: нестабильная сеть или перегруженная камера |
| CPU в долях отведённых ядер | hastreamer_process_cpu_cores ÷ hastreamer_worker_cores |
стабильно выше 0,85: пора добавлять ядра или сервер |
| Свободное место архива | раздел Storage, строка прогноза |
прогноз заполнения короче срока хранения, который вам нужен |
| Потери приёма UDP | hastreamer_udp_ingest_*_total |
счётчик растёт: мал буфер сокета, см. udp_ingest_rcvbuf |
| Потери у зрителей | раздел Sessions, фильтр Loss > 0 |
доля потерь растёт у многих сессий сразу: узкая исходящая полоса |
| Задержки доставки | колонка Lag в Sessions |
ненулевая у многих: сервер не успевает отдавать |
| Открытые дескрипторы | hastreamer_open_fds |
приближается к системному пределу |
| Обрезка метрик | hastreamer_streams_omitted |
больше нуля: часть потоков не видна системе мониторинга |
Process CPU с %cpu из topПоказатель считается как дельта процессорного времени, делённая на число ядер, и выражается в
ядрах: 0.12 — примерно одна восьмая ядра. Мгновенная выборка top — другая величина, и совпадать
они не обязаны.
Куда дальше#
- Веб-интерфейс — где в панели находится каждый из этих показателей.
- Конфигурация и система —
GET /system,GET /totals, журналы и аудит. - Готовые сценарии — проверка здоровья одной командой.
- Диагностика неисправностей — что делать, когда показатель ушёл в красное.