Перейти к содержимому

Сейчас: внедряю системы ИИ-мониторинга в Steady Control

Daniil Dushin

Uptime Monitor

Следит за роутерами, камерами и точками Wi-Fi и сообщает в Telegram в ту же минуту, когда что-то из них упало.

Роль
Личный проект — проектирование, код, тесты
Год
2026
Категория
Инфраструктура · Веб
Стек
Python 3.12, asyncio, FastAPI, HTMX
Обложка Uptime Monitor — ряд полос аптайма, одна из них красная

Проблема

В отеле, ресторане или небольшом офисе камеру или точку Wi-Fi, которая перестала работать, обычно замечают через несколько дней: когда понадобилась запись, которой нет, или когда гости жалуются на Wi-Fi.

Большие системы мониторинга вроде Zabbix или PRTG слишком тяжелы для места без IT-отдела. Я профессионально устанавливаю камеры, регистраторы и сети MikroTik, поэтому хотел один небольшой процесс и один YAML-файл.

Решение

  • Устройства перечислены в devices.yaml: имя, IP-адрес, ping вкл./выкл. и TCP-порты для проверки (554 для IP-камеры, 9100 для принтера).
  • Каждые 60 секунд все устройства проверяются параллельно через asyncio: ping плюс TCP-подключение к каждому порту.
  • Устройство считается упавшим только после двух неудачных раундов подряд, а поднявшимся — после двух удачных. Один потерянный ping или «мигающая» точка доступа не засыпают чат.
  • Тревоги в Telegram говорят, что именно не работает («ping: no reply; tcp/554: timed out»), а при восстановлении — сколько длился простой.
  • Страница статуса (FastAPI + HTMX, обновляется каждые 10 секунд) показывает аптайм за 24 часа и 7 дней, полосы по часам и последние аварии.
  • Состояния устройств сохраняются, поэтому после перезапуска одна и та же авария не объявляется дважды.

Демо

Промо-ролик, собранный в Remotion.

Скриншоты

Страница статуса с восемью устройствами, аптаймом за 24 часа и 7 дней, полосами по часам и одной упавшей точкой Wi-Fi
Страница статуса в демо-режиме, одна точка доступа упала.

Технологии

  • Python 3.12
  • asyncio
  • FastAPI
  • HTMX
  • Jinja2
  • aiogram 3
  • SQLite
  • pytest

Ping использует системную команду ping, а не «сырые» сокеты, которым нужны права администратора. Адреса устройств проверяются до того, как попадут в командную строку, поэтому адрес, который начинается с «-», не может стать опцией ping. Аптайм — это удачные проверки, делённые на все проверки за период: один SQL-запрос.

Фрагмент кода

Решение «упал / поднялся» — чистая функция. Простой начинается с первой неудачной проверки и заканчивается первой удачной проверкой восстановления, а не в моменты отправки тревог:

python
def apply(state, *, device, ok, at, rules, problems=()):
    if ok:
        first_success = state.first_success_at or at
        state = replace(state, failures=0, first_failure_at=None,
                        successes=state.successes + 1, first_success_at=first_success)
        if state.status is Status.UNKNOWN:
            return replace(state, status=Status.UP), None
        if state.status is Status.DOWN and state.successes >= rules.recover_after:
            event = Event(EventKind.UP, device, first_success, state.down_since)
            return replace(state, status=Status.UP, down_since=None), event
        return state, None

    first_failure = state.first_failure_at or at
    state = replace(state, successes=0, first_success_at=None,
                    failures=state.failures + 1, first_failure_at=first_failure)
    if state.status is not Status.DOWN and state.failures >= rules.fail_after:
        event = Event(EventKind.DOWN, device, at, first_failure, problems)
        return replace(state, status=Status.DOWN, down_since=first_failure), event
    return state, None

Баг, который я нашёл и исправил

На неанглийской Windows не измерялась задержка ping

Что сломалось

Когда я запустил проверки на своей сети, роутер был «UP», но его задержка всегда оставалась пустой. Тесты с английскими примерами вывода никак не могли это поймать.

Причина

На моём компьютере ping отвечает по-русски, в старой кодировке консоли cp866: время<1мс TTL=128. Проверка декодирует вывод как ASCII: TTL=128 сохраняется, а каждая русская буква превращается в знак-заменитель. Шаблон задержки искал единицу ms или мс, а кириллического мс после декодирования уже не было.

Исправление

Совсем не опираемся на слово «миллисекунды». Linux и английская Windows распознаются по time=12.3 ms, любая другая Windows — по числу прямо перед TTL=, которое печатает каждая языковая версия. Тесты теперь используют настоящий русский вывод, закодированный в cp866 и декодированный точно так же, как это делает проверка.

До
_LATENCY = re.compile(r"[=<]\s*(\d+(?:[.,]\d+)?)\s*(?:ms|мс)", re.IGNORECASE)

match = _LATENCY.search(output) if ok else None
После
_LATENCY_LATIN = re.compile(r"time[=<]\s*(\d+(?:[.,]\d+)?)\s*ms", re.IGNORECASE)
_LATENCY_BEFORE_TTL = re.compile(r"[=<]\s*(\d+(?:[.,]\d+)?)\S*\s+TTL=")

match = _LATENCY_LATIN.search(output) or _LATENCY_BEFORE_TTL.search(output)

Результат

  • 52 автоматических теста, включая настоящий вывод ping с русской Windows, английской Windows и Linux.
  • Проверено на настоящей сети: домашний роутер, DNS Cloudflare, закрытый порт и неизвестное имя хоста — у каждого правильный результат и причина.
  • Демо-режим моделирует восемь устройств и генерирует неделю истории примерно за секунду.