| 2026-10-10 |

feat: история прореживается равными интервалами времени, а не равным числом точек
...
Нарезка по счёту раздувала редкие ярусы хранения до ширины сырых суток: в
запросе за неделю точки младше суток занимали ~95 % ширины графика, а шесть
старших суток ужимались в остаток — ось не имела со временем ничего общего.
Теперь окно от первой точки до последней делится на max_points слотов равной
длительности, в каждом остаётся последняя точка (дайджесты сливаются в неё) —
на 7 сут доля точек младше суток упала с ~95 % до 37 % при её доле времени 1/7.
Остаток перекоса — от того, что ось категорийная: где ярус реже, точек на
единицу времени меньше. Пропорциональность до конца дал бы только временной
масштаб оси (линейный x по ts вместо индекса) — это отдельная правка.
Заодно: ответ за неделю стал в разы меньше (231 точка / 109 КБ вместо
1500 / 580 КБ — точки не плодятся там, где данные редкие), а disks_json
разбирается только у выживших точек (agg — у всех: его крайности сливаются
в оставшуюся).
Eugene Sukhodolskiy
committed
13 hours ago
|

fix: история за период не обрезается по свежему краю — прореживание по всему окну
...
На реальном интервале 30 с окно 24 ч — это 2880 точек, неделя — ~7 тыс., а
запрос ограничивался LIMIT'ом по свежему краю: страница сервера просит 1500
точек, поэтому «7 d» показывал последние ~7 часов, а «24 h» — ~12. Сырые сутки
вытесняли всю старую историю вместе с дайджестами, и полосы min–max на длинных
периодах вообще не доезжали до графика.
Теперь `limit` — это «сколько точек вернуть максимум»: в окне читается до
FETCH_CAP строк, и если их больше limit, точки прореживаются равномерно по
всему периоду (первая — начало запрошенного интервала, последняя — сейчас),
а дайджесты выброшенных сливаются в оставшуюся. Прореживание вынесено в общий
`app/digest.py` (им же пользуется MCP-тул server_history — у него была та же
обрезка на окне 168 ч); в группе остаётся последняя точка, так что свежий край
не теряется.
Проверено на демо-данных: 7 d отдаёт окно целиком (03.10 → 10.10),
час со спайком доезжает как cpu max 97.5 и /data max 88, часы простоя — как
n < exp, тултип полосы показывает «23.5–32% · замеров 240/240».
Eugene Sukhodolskiy
committed
13 hours ago
|

feat: метрики — дайджест бакета при разряжении (крайности и простои не теряются)
...
Прореживание оставляло одну реальную точку на бакет, поэтому всплеск CPU под
98% две минуты или падение, пришедшееся ровно на удалённую точку, исчезали с
графика навсегда. Теперь вместе с выжившей точкой пишется дайджест интервала
в metrics.agg_json: n/exp (пришло замеров против ожидаемых — n < exp значит
простой), min/max по cpu/ram/swap/load/сеть и по каждому маунту диска.
Пишется только там, где бакет реально схлопывается (n > 1), поэтому сырые
сутки остаются сырыми, а повторный прогон ничего не перезаписывает.
- metrics_retention: _Bucket копит крайности; границы бакета для DELETE
собираются через datetime (корректный перенос через час/сутки), удаление —
по индексу, без списка id в памяти.
- схема + ALTER: metrics.agg_json.
- API /servers/{id}/metrics и MCP server_history отдают точку с полем agg;
прореживание внутри тула сливает дайджесты выкинутых точек, а не
выбрасывает их вместе с крайностями.
Eugene Sukhodolskiy
committed
14 hours ago
|

perf: метрики — тяжёлые JSON в server_state, разряжение истории по возрасту
...
Строка метрик худеет с ~5 КБ JSON до ~0.1 КБ: net_json (сырые счётчики),
docker_json, processes_json и extra нужны только от ПОСЛЕДНЕГО пакета, в
истории они были мёртвым грузом (~89% объёма). Теперь они живут в одной
строке server_state на сервер:
- ingest читает предыдущий пакет из server_state (fallback на последнюю
строку метрик — плавный апгрейд на живом деплое), пишет состояние UPSERT;
- GET /servers/{id} берёт процессы/docker/extra из состояния через
LEFT JOIN — контракт API для фронта не менялся;
- в history остаются числа и disks_json (нужны графикам на всю глубину).
Разряжение (app/metrics_retention.py) по возрасту точки: 0–24 ч — всё,
24 ч–3 сут — 1/мин, 3–7 сут — 1/5 мин, 7–365 сут — 1/час, старше года —
удаление. В бакете остаётся реальная последняя точка (бакеты считаются
срезом строки ISO-8601, без парсинга дат). Петля — при старте и каждые
15 минут, затем PRAGMA incremental_vacuum.
Чтобы удалённое возвращалось файлу: auto_vacuum=INCREMENTAL, а на БД,
созданных до этого — разовый VACUUM при старте панели.
Eugene Sukhodolskiy
committed
14 hours ago
|
| 2026-09-10 |
feat: stage 1 — panel skeleton (FastAPI + SQLite) and ingest API
...
- FastAPI backend: config (pydantic-settings, GHARD_* env), aiosqlite + WAL
- SQLite schema: servers, metrics, events
- POST /api/v1/ingest: auth by X-Server-Key (sha256 hash), net rates in MB/s
computed on panel from raw counter deltas, server card upsert
- Servers API: create (key shown once), list with dashboard summary,
detail, PATCH name/note, delete, metrics history
- Dockerfile + docker-compose with persistent volume
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Sep
|