"""Мониторинг: сбор метрик по SSH, снапшоты в БД.""" from __future__ import annotations from typing import Optional from sqlmodel import select from app.core.ssh import SSHClient from app.db.database import get_session from app.db.models import MetricSnapshot # Каждая команда выводит одну строку "KEY value..."; парсер ниже. # CPU считаем по дельте двух замеров /proc/stat за 1 секунду. _METRICS_CMD = ( # CPU: первая строка cpu в /proc/stat (user nice system idle iowait irq softirq steal) "read cpu u n s idle iow irq sirq st rest < /proc/stat; " "t1=$((u+n+s+idle+iow+irq+sirq+st)); i1=$((idle+iow)); " "sleep 1; " "read cpu u n s idle iow irq sirq st rest < /proc/stat; " "t2=$((u+n+s+idle+iow+irq+sirq+st)); i2=$((idle+iow)); " "dt=$((t2-t1)); di=$((i2-i1)); " "if [ $dt -gt 0 ]; then awk -v a=$dt -v b=$di 'BEGIN{printf \"CPU %.1f\\n\", (a-b)*100/a}'; else echo 'CPU 0.0'; fi; " # RAM: used/total в МБ "free -m | awk 'NR==2{printf \"MEM %d %d\\n\", $3, $2}'; " # Диск: процент занятого на / "df -m / | awk 'NR==2{printf \"DISK %.1f\\n\", $3*100/$2}'; " # Load average 1min "awk '{printf \"LOAD %s\\n\", $1}' /proc/loadavg; " # Сеть: суммарные байты rx/tx всех интерфейсов "awk '/: /{rx+=$2; tx+=$10} END{printf \"NET %d %d\\n\", rx, tx}' /proc/net/dev" ) async def collect(c: SSHClient, server_id: int, prev_net: Optional[tuple] = None) -> MetricSnapshot: r = await c.run(_METRICS_CMD, timeout=30) cpu = mem_used = mem_total = disk = load = 0.0 rx = tx = 0 for line in r.stdout.splitlines(): p = line.split() if len(p) < 2: continue if p[0] == "CPU": cpu = float(p[1]) elif p[0] == "MEM": mem_used, mem_total = int(p[1]), int(p[2]) elif p[0] == "DISK": disk = float(p[1]) elif p[0] == "LOAD": load = float(p[1]) elif p[0] == "NET": rx, tx = int(p[1]), int(p[2]) rx_kbs = tx_kbs = 0.0 if prev_net: dt = 60.0 # интервал опроса rx_kbs = max(0.0, (rx - prev_net[0]) / 1024 / dt) tx_kbs = max(0.0, (tx - prev_net[1]) / 1024 / dt) snap = MetricSnapshot( server_id=server_id, cpu_percent=round(cpu, 1), mem_percent=round(mem_used * 100 / mem_total, 1) if mem_total else 0, mem_used_mb=mem_used, mem_total_mb=mem_total, disk_percent=disk, load1=load, net_rx_kbs=round(rx_kbs, 1), net_tx_kbs=round(tx_kbs, 1), ) with get_session() as s: s.add(snap) s.commit() return snap def latest(server_id: int) -> Optional[MetricSnapshot]: with get_session() as s: return s.exec( select(MetricSnapshot) .where(MetricSnapshot.server_id == server_id) .order_by(MetricSnapshot.id.desc()) .limit(1) ).first() def history(server_id: int, limit: int = 60) -> list[MetricSnapshot]: with get_session() as s: rows = s.exec( select(MetricSnapshot) .where(MetricSnapshot.server_id == server_id) .order_by(MetricSnapshot.id.desc()) .limit(limit) ).all() return list(reversed(rows))