95 lines
3.4 KiB
Python
95 lines
3.4 KiB
Python
|
|
"""Мониторинг: сбор метрик по 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))
|