feat(instance): метрики и healthcheck инстанса для дашборда

Раздел «Инстанс» получает живые показатели сервера (AGENT.md 7.19).

- `internal/sysinfo`: загрузка процессора по cgroup v2 (`cpu.stat`/`cpu.max`)
  с откатом на `/proc/stat`, память по лимиту контейнера либо `/proc/meminfo`,
  место на разделе данных и размер базы вместе с журналом WAL;
- `GET /api/v1/instance/metrics` (только администратор инстанса): метрики,
  состояние (`ok|warn|failed`), проверки app/database/disk/storage/voice/
  gateway, версия и время работы; недоступные источники не ломают ответ, а
  попадают в проверку `metrics`;
- тяжёлые проверки (БД, SFU, запись) кэшируются на 5 секунд: ручку можно
  опрашивать раз в секунду;
- `voice.IssueRoomList` и `AdminClient.Ping` проверяют доступность RoomService
  и валидность ключей LiveKit (та же связка, что ломала модерацию);
- у метрик свой лимит частоты, они исключены из общего лимита API
  (`RateLimiter.MiddlewareExcept`), иначе открытый раздел съедал бы половину
  бюджета запросов;
- тесты: `internal/sysinfo` (cgroup, /proc, память, диск, размеры файлов) и
  `internal/server/api_metrics_test.go` (доступ, состав метрик, кэш, 429).
This commit is contained in:
2026-09-20 21:26:17 +03:00
parent 8ae8ca1a1d
commit 4c3736b5fb
8 changed files with 1128 additions and 19 deletions
+20 -2
View File
@@ -110,17 +110,35 @@ func (c *AdminClient) ListParticipants(ctx context.Context, room string) ([]Part
return response.Participants, nil
}
// Ping проверяет доступность RoomService: healthcheck дашборда заодно
// убеждается, что ключи LiveKit валидны (их неверность ломает модерацию).
func (c *AdminClient) Ping(ctx context.Context) error {
if !c.Enabled() {
return ErrDisabled
}
token, err := c.issuer.IssueRoomList()
if err != nil {
return err
}
return c.callWithToken(ctx, token, "ListRooms", map[string]any{}, nil)
}
// call выполняет запрос к Twirp-методу RoomService с административным токеном
// этой комнаты: LiveKit принимает `roomAdmin` только вместе с именем комнаты.
func (c *AdminClient) call(ctx context.Context, room, method string, body any, out any) error {
if !c.Enabled() {
return ErrDisabled
}
payload, err := json.Marshal(body)
token, err := c.issuer.IssueAdmin(room)
if err != nil {
return err
}
token, err := c.issuer.IssueAdmin(room)
return c.callWithToken(ctx, token, method, body, out)
}
// callWithToken выполняет запрос к Twirp-методу с готовым токеном.
func (c *AdminClient) callWithToken(ctx context.Context, token, method string, body any, out any) error {
payload, err := json.Marshal(body)
if err != nil {
return err
}