Files
cloud-ip-validator/docs/changes/2026-10-03_16-39_analytics-section-plan.md
T
ayurishchevandClaude Sonnet 5.5 db73409e8f Add plans for the analytics section and for verdicts without late results
- docs/changes: plan of the "Аналитика" section (runs as selectable
  timestamps, API, page layout, acceptance numbers from the 6440-address run)
  and plan of the verdict-integrity change (causes found in the code and in
  the data, decisions, tests, rollout).
- docs/mockups/analytics-mockup.html: self-contained HTML mockup of the
  analytics page for review in a browser.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-03 17:59:40 +03:00

20 KiB
Raw Blame History

План: раздел «Аналитика»

Статус: план, код не менялся. Основа — прогон 6440 адресов (2026-10-02 13:47 – 22:29 UTC) и три отчёта в analysis/.

1. Зачем

Сейчас выводы по прогону получены ручными запросами к БД: срезы по подсетям, провалы по целям, ingress по площадкам, расхождение вердикта и проверок. Нужна страница, которая показывает то же самое постоянно, для любого прогона, без SQL.

Главное требование: переключать отметку времени — завершённый запуск проверки, — чтобы данные разных запусков не смешивались.

2. Ключевая находка: «запуска» в данных нет

cycle_id считается на адрес (ip_registry.next_cycle), а не на весь прогон. В БД сейчас: цикл 1 у 6431 адреса, циклы 2–4 у 9 перепроверенных. По cycle_id нельзя выбрать «прогон от 2 октября»: он ничего не группирует.

Группировка по времени тоже ненадёжна: прогон 6440 адресов шёл 8 ч 42 мин, а перепроверки внутри него шли в то же время.

Поэтому нужна новая сущность — запуск (run). Это единственное изменение схемы, без него переключатель невозможен.

3. Модель данных (миграция 0010)

Новые таблицы:

  • check_runs(id, kind, started_at, finalized_at, state). kind: auto (автоцикл), manual (добавление, скан, перепроверка). state: open или finalized.
  • run_results(run_id, registry_id, ip_address, cycle_id, verdict, aggregated_at, expected_checks, recorded_at_aggregation). Одна строка на адрес в запуске. Нужна, потому что вердикт сейчас хранится только в ip_queue.overall_result и затирается при перепроверке.
  • subnets(cidr PRIMARY KEY, label). Список подсетей заказчика (45 шт.) не лежит ни в БД, ни в репозитории, его задаёт администратор.

Новые колонки: run_id в ip_queue и checks. Индекс idx_checks_run(run_id, registry_id).

Правила запуска

  1. Новый запуск открывается, когда в очередь попадает адрес, а открытого запуска нет. Автоцикл открывает запуск явно в начале фазы.
  2. Пока запуск открыт, все добавленные и перепроверяемые адреса входят в него.
  3. Запуск завершается, когда все его строки очереди в конечном состоянии (done, failed, occupied, отмена). Тогда finalized_at = последняя агрегация.
  4. Перепроверка после завершения открывает новый запуск. Поэтому старый запуск не меняется.
  5. Если адрес перепроверен внутри открытого запуска, в аналитике берётся его последний цикл этого запуска.
  6. Поздние результаты (пришли после агрегации, как у 50 адресов в анализе 6.5) остаются в своём запуске: run_id берётся из строки очереди при записи проверки. Их число показывается отдельно.

Заполнение уже накопленных данных

Запуски выделяются по паузе: если между окончаниями соседних циклов больше 60 минут, начинается новый запуск. Максимальная пауза в текущей БД 0,8 минуты, поэтому получится один запуск на 6440 адресов, 9 перепроверок войдут в него. Вердикт берётся из ip_queue.overall_result, для прежних циклов без него считается по проверкам и помечается как расчётный.

4. API (control-api, только чтение)

  • GET /api/v1/admin/analytics/runs — список запусков: id, тип, начало, конец, адресов, доли pass/partial/fail, состояние. Открытый запуск виден, но помечен как идущий.
  • GET /api/v1/admin/analytics/runs/{id} — все блоки страницы одним ответом (десятки КБ):
    • summary: адресов; вердикты; Egress OK и Ingress OK по фактическим проверкам; доли по типам проверок; индикаторы прикладного уровня: адреса, провалившие все egress-проверки https (на запуске 02.10: 307, из них по всем 5 целям 290), и адреса, провалившие все ingress-проверки ssh (7; хотя бы с одной площадки 222); длительность и скорость; число поздних результатов; число неполных наборов.
    • partial_reasons: причины partial (только egress; ingress и egress; только ingress; неполный набор; только неполный набор).
    • subnets: по каждой подсети — адресов, pass, Egress OK, Ingress OK.
    • targets: egress по цели и типу проверки (процент провалов); матрица «подсеть × цель» для выбранного типа.
    • sites: ingress по площадке и типу проверки.
    • validators: доля провалов egress по валидаторам.
    • errors: классы ошибок по уровню и типу (таймаут, нет маршрута, баннер SSH и др.), классификатор по checks.detail.
    • data_quality: поздние результаты, неполные наборы, адреса, где вердикт расходится с проверками.
  • GET/PUT /api/v1/admin/config/subnets — список подсетей (замена целиком, текстом по одной в строке).

Группировка по типу проверки везде одна и та же, правило CheckFamily из internal/db/models.go (tcp-22 и tcp-443 → tcp). Новый тип появляется в таблицах сам.

Адрес → подсеть определяется в Go по самому длинному совпавшему префиксу (SQLite не умеет работать с CIDR). Для 6440 адресов это миллисекунды.

Расчёт идёт одним проходом по checks запуска (~190 тыс. строк). Результат завершённого запуска кэшируется в памяти по run_id и MAX(checks.id) запуска, чтобы поздний результат сбросил кэш.

5. Макет страницы /analytics?run=<id>

┌─ Аналитика ─────────────────────────────────────────────────────────────┐
│ Запуск:  ◀  [ 02.10 13:47 → 22:29 · авто · 6440 адр · 30% pass ▾ ]  ▶    │
│          (идущий запуск виден в списке, но недоступен: «идёт, 120 из 800») │
├──────────────────────────────────────────────────────────────────────────┤
│ [Адресов 6440] [pass 1962 · 30%] [partial 4478 · 70%] [fail 0]           │
│ [Egress OK 31%] [Ingress OK 97%] [Длительность 8 ч 42 м] [Поздних 246]   │
├──────────────────────────────────┬───────────────────────────────────────┤
│ Почему partial                   │ Данные: качество                      │
│ ▇▇▇▇▇▇▇▇▇▇▇▇ только egress  4146 │ Поздние результаты       246 (50 адр.)│
│ ▇ ingress+egress             157 │ Неполный набор           167 адресов  │
│ ▇ egress+неполный набор      125 │ Вердикт ≠ проверки        50 адресов  │
│ ▏ только ingress               8 │                                       │
├──────────────────────────────────┴───────────────────────────────────────┤
│ Подсети      [сорт: хуже всего ▾] [показать все 43]                      │
│ подсеть          адр.  pass%        Egress OK%     Ingress OK%           │
│ 161.104.108.0/23  45   ░░░░░ 0%     ░░░░░ 0%       ▇▇▇▇▇ 100%            │
│ 83.166.248.0/21  659   ▇░░░░ 11%    ▇░░░░ 12%      ▇▇▇▇░ 91%      →реестр│
├──────────────────────────────────────────────────────────────────────────┤
│ Egress по целям   тип: [https] [icmp] [все]                              │
│ цель                  провал   |  подсеть × цель (тепловая карта)         │
│ packages.ubuntu.com    60%     |  83.166.248.0/21   ■■□■■                │
│ dl-cdn.alpinelinux.org 34%     |  212.233.72.0/21   ■■□■■                │
├──────────────────────────────────────────────────────────────────────────┤
│ Ingress по площадкам   площадка × тип (tcp/ssh/icmp)  |  классы ошибок   │
├──────────────────────────────────────────────────────────────────────────┤
│ Валидаторы: полоса «доля провалов egress» по 20 валидаторам (≈33–38%)    │
└──────────────────────────────────────────────────────────────────────────┘
  • Два индикатора прикладного уровня (Egress https и Ingress ssh: все провалены) кликабельны: клик открывает окно со списком адресов и кнопкой «Скачать CSV». В рабочей странице список берётся из GET /admin/analytics/runs/{id}/addresses?list=egress_https_all_failed|ingress_ssh_all_failed (CSV собирается на сервере, Content-Disposition: attachment, UTF-8 с BOM), чтобы не зависеть от размера страницы.
  • Каждая строка блока «Классы ошибок ingress» кликабельна: окно показывает распределение проваленных проверок класса по валидаторам, вердикт адреса, статус проверки («в вердикте» или «после вердикта») и список с кнопкой «Скачать CSV». Эндпоинт: GET /admin/analytics/runs/{id}/errors/{class} (JSON и ?format=csv).
  • Выбор запуска хранится в адресной строке (?run=), ссылку можно переслать. По умолчанию — последний завершённый запуск.
  • Каждый блок рассчитан только на выбранный запуск. Данных других запусков на странице нет.
  • Строка подсети, ячейка матрицы и причина partial — ссылки в «Реестр» с фильтром, чтобы увидеть конкретные адреса. Для этого в GET /admin/registry и /registry добавляются параметры run, subnet, target, reason; страница «Реестр» тоже учитывает выбранный запуск.
  • Рисование: HTML и встроенный SVG на сервере, без библиотек графиков (в дашборде их нет). Цвета и тепловая карта — по руководству навыка dataviz, с тёмной темой, как в остальном дашборде.
  • Пункт «Аналитика» в боковом меню (internal/dashboard/templates/layout.html).

6. Файлы

  • БД: internal/db/migrations/0010_check_runs.sql, models.go, queries_runs.go, правки queries_ipqueue.go (SubmitIPs, FinishIP, CancelIP, ClearAllIPs) и queries_checks.go (UpsertCheck ставит run_id).
  • Оркестратор: internal/orchestrator/orchestrator.go — запись run_results при агрегации, завершение запуска; автоцикл открывает запуск.
  • Аналитика: новый пакет internal/analytics (расчёт блоков, классификатор ошибок, подсети).
  • API: internal/httpapi/handlers_analytics.go, routes.go, docs/API.md.
  • Дашборд: internal/dashboard/handlers_analytics.go, templates/analytics.html, static/dashboard.css, пункт меню, форма списка подсетей на /settings.

7. Поставка тремя шагами

  1. Запуски: миграция, правила, заполнение накопленных данных. Результат: в БД один запуск на 6440 адресов. Видимых изменений в UI нет.
  2. API аналитики: расчёт и эндпоинты.
  3. Страница: макет выше, фильтры в реестре.

Каждый шаг отдельно собирается, тестируется и выкатывается на стенд.

8. Проверка

  • Контрольные числа: расчёт по запуску на копии боевой БД (rxprod-compose/capi-db) должен дать цифры отчётов analysis/: 6440 адресов, 1962 pass / 4478 partial; провал egress у 4437; провал HTTPS по packages.ubuntu.com у 3841; 290 адресов провалили все цели; ingress провален у 224; 50 pass с поздними провалами; 167 неполных наборов. Если цифры не сходятся, расчёт неверен.
  • Миграция на копии боевой БД: ровно один запуск, run_id заполнен у всех checks и ip_queue, повторный запуск миграции безопасен.
  • Тесты: изоляция запусков (два запуска с разными результатами у одного адреса не пересекаются); перепроверка после завершения открывает новый запуск, внутри открытого — нет; поздний результат попадает в свой запуск; идущий запуск не выбирается; новый тип проверки (dns) появляется в таблицах; подсети (самый длинный префикс, адрес вне списка → «прочие»).
  • Нагрузка: страница на запуске 6440 адресов, расчёт до ~1 с, повторный запрос из кэша быстрее; EXPLAIN QUERY PLAN по idx_checks_run.
  • Вручную на стенде: выбрать запуск, сверить ключевые числа с отчётами, пройти по ссылке из подсети в реестр.

9. Допущения и ограничения

  • Валидатор у ingress-проверок. В checks.validator_id для проверок пробера пусто. Валидатор адреса в цикле берётся из события fip_associated (поле validator_id в payload) по registry_id и cycle_id. На запуске 02.10 все 950 проваленных ingress-проверок нашли валидатора. Надёжнее писать validator_id в ingress-проверку при приёме результата; это отдельное маленькое изменение шага 1.

  • Классификатор ошибок работает по checks.detail и типу проверки; на запуске 02.10 он даёт 7 классов с суммой 950 (SSH: таймаут 327, ICMP: нет ответа 276, TCP-22: таймаут 260, SSH: баннер «Not allowed» 47, SSH: нет маршрута 18, ICMP: time exceeded 15, TCP-22: нет маршрута 7). Новые тексты ошибок попадают в класс «прочее» и видны в списке.

  • Новое наблюдение для блока «Качество данных». 844 из 950 проваленных ingress-проверок (89%) записаны после агрегации вердикта (среди всех ingress-проверок после вердикта пришло 1430 из 76644, около 1,9%). То есть таймауты приходят позже вердикта систематически, а не случайно. В отчёте анализа 6.5 это видно только для 246 провалов у адресов pass.

  • Список подсетей даёт администратор (форма на /settings). Пока он пуст, подсети группируются автоматически по /24.

  • Порог 60 минут для заполнения накопленных данных — только для старых записей, настройкой не делается.

  • Глубина истории (history_retention_cycles > 0) удаляет старые проверки, и аналитика старых запусков теряет детали. По умолчанию 0 (хранить всё). Вердикты в run_results остаются.

  • Перед выкладкой миграции обязательна копия rxprod-compose/capi-db/control-api.db.

  • Вне объёма (следующий этап): сравнение двух запусков, выгрузка в CSV, оповещения.

  • Вердикт системы и фактические проверки считаются отдельно и показаны рядом (по анализу 6.5 они расходятся у 50 адресов), чтобы поздние результаты не скрывались.

10. Порядок по правилам проекта

План (этот файл) → реализация по шагам из п.7 → …-summary.md на каждый шаг → обновление README.md и docs/ → обновление графа.