start
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ...core.db import get_db
|
||||
from ...models.device import Device
|
||||
from ...models.metric import DeviceMetric
|
||||
from ...models.user import User
|
||||
from ...schemas.metric import MetricPoint
|
||||
from ..deps import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/devices/{device_id}/metrics", response_model=list[MetricPoint])
|
||||
def get_metrics(
|
||||
device_id: int,
|
||||
hours: int = 24,
|
||||
db: Session = Depends(get_db),
|
||||
_: User = Depends(get_current_user),
|
||||
) -> list[MetricPoint]:
|
||||
if not db.get(Device, device_id):
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, "device not found")
|
||||
since = datetime.now(timezone.utc) - timedelta(hours=max(1, min(hours, 24 * 30)))
|
||||
rows = (
|
||||
db.query(DeviceMetric)
|
||||
.filter(DeviceMetric.device_id == device_id, DeviceMetric.created_at >= since)
|
||||
.order_by(DeviceMetric.created_at.asc())
|
||||
.all()
|
||||
)
|
||||
return [
|
||||
MetricPoint(
|
||||
ts=r.created_at,
|
||||
cpu_load=r.cpu_load,
|
||||
mem_used_pct=r.mem_used_pct,
|
||||
uptime_seconds=r.uptime_seconds,
|
||||
internet_ok=r.internet_ok,
|
||||
rx_bps=r.rx_bps,
|
||||
tx_bps=r.tx_bps,
|
||||
)
|
||||
for r in rows
|
||||
]
|
||||
|
||||
|
||||
@router.get("/heartbeat")
|
||||
def heartbeat(
|
||||
hours: float = 24,
|
||||
bins: int = 48,
|
||||
db: Session = Depends(get_db),
|
||||
_: User = Depends(get_current_user),
|
||||
) -> dict:
|
||||
"""Сводка статусов всех устройств по бинам времени для heartbeat-графика.
|
||||
|
||||
Каждый бин получает один из статусов:
|
||||
- "up" — есть метрика, internet_ok != False
|
||||
- "no-net" — есть метрика, internet_ok == False
|
||||
- "down" — нет ни одной метрики в окне
|
||||
- "none" — нет данных вообще
|
||||
Приоритет внутри бина: down/no-net > up.
|
||||
"""
|
||||
hours = max(0.25, min(float(hours), 24 * 7))
|
||||
bins = max(6, min(bins, 288))
|
||||
now = datetime.now(timezone.utc)
|
||||
since = now - timedelta(hours=hours)
|
||||
bin_seconds = (hours * 3600) / bins
|
||||
# Один сэмпл «закрашивает» окно вокруг себя, чтобы не было полосатости,
|
||||
# когда интервал опроса больше длины бина (например, 1 мин probe и 30 сек бин).
|
||||
halo_seconds = max(bin_seconds * 1.5, 90.0)
|
||||
|
||||
devices = db.query(Device).order_by(Device.name.asc()).all()
|
||||
rows = (
|
||||
db.query(DeviceMetric)
|
||||
.filter(DeviceMetric.created_at >= since - timedelta(seconds=halo_seconds))
|
||||
.order_by(DeviceMetric.created_at.asc())
|
||||
.all()
|
||||
)
|
||||
by_dev: dict[int, list[DeviceMetric]] = {}
|
||||
for r in rows:
|
||||
by_dev.setdefault(r.device_id, []).append(r)
|
||||
|
||||
# Приоритет: no-net побеждает up; down/none перекрываются любой выборкой.
|
||||
def _promote(cur: str, new: str) -> str:
|
||||
if new == "no-net":
|
||||
return "no-net"
|
||||
if cur in ("none", "down") and new == "up":
|
||||
return "up"
|
||||
return cur
|
||||
|
||||
out_devices = []
|
||||
for dev in devices:
|
||||
buckets = ["none"] * bins
|
||||
for r in by_dev.get(dev.id, []):
|
||||
ts = r.created_at
|
||||
if ts.tzinfo is None:
|
||||
ts = ts.replace(tzinfo=timezone.utc)
|
||||
offset = (ts - since).total_seconds()
|
||||
lo = int((offset - halo_seconds) // bin_seconds)
|
||||
hi = int((offset + halo_seconds) // bin_seconds)
|
||||
new_state = "no-net" if r.internet_ok is False else "up"
|
||||
for idx in range(max(0, lo), min(bins, hi + 1)):
|
||||
buckets[idx] = _promote(buckets[idx], new_state)
|
||||
out_devices.append({
|
||||
"id": dev.id,
|
||||
"name": dev.identity or dev.name,
|
||||
"host": dev.host,
|
||||
"status": dev.status,
|
||||
"buckets": buckets,
|
||||
})
|
||||
return {
|
||||
"since": since.isoformat(),
|
||||
"until": now.isoformat(),
|
||||
"bins": bins,
|
||||
"hours": hours,
|
||||
"devices": out_devices,
|
||||
}
|
||||
Reference in New Issue
Block a user