fix(metrics): correct cpu/disk% chart scaling, evict stale collector state, lighten nic query, drop polling & dead RRD code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ from ...ipam_service import IPAMService
|
||||
from ._helpers import (check_vm_access, require_vm_access, _get_proxmox_client,
|
||||
get_next_vmid, archive_and_delete_snapshots,
|
||||
save_vm_instance, get_vm_instance, soft_delete_vm_instance)
|
||||
from ...services.metrics_history import query_instance_metrics, timeframe_to_range
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -1848,7 +1849,6 @@ def get_container_rrddata(
|
||||
# ==================== Metrics History (DB-only) ====================
|
||||
|
||||
def _resolve_metrics_window(timeframe, from_ts, to_ts):
|
||||
from ...services.metrics_history import timeframe_to_range
|
||||
from ...config import settings
|
||||
now = int(time_lib.time())
|
||||
if from_ts is not None and to_ts is not None:
|
||||
@@ -1874,7 +1874,6 @@ def get_vm_metrics(
|
||||
current_user: User = Depends(PermissionChecker("vm:view")),
|
||||
):
|
||||
"""Retrieve VM metrics time-series from the local DB (no Proxmox call)."""
|
||||
from ...services.metrics_history import query_instance_metrics
|
||||
f, t = _resolve_metrics_window(timeframe, from_ts, to_ts)
|
||||
return query_instance_metrics(db, server_id, vmid, f, t, nic)
|
||||
|
||||
@@ -1892,7 +1891,6 @@ def get_container_metrics(
|
||||
current_user: User = Depends(PermissionChecker("vm:view")),
|
||||
):
|
||||
"""Retrieve container metrics time-series from the local DB (no Proxmox call)."""
|
||||
from ...services.metrics_history import query_instance_metrics
|
||||
f, t = _resolve_metrics_window(timeframe, from_ts, to_ts)
|
||||
return query_instance_metrics(db, server_id, vmid, f, t, nic)
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ def collect_once(db, items, clients, prev, _now=None):
|
||||
|
||||
# netstat is one call per (server_id, node); cache per tick.
|
||||
netstat_cache: dict[tuple, list] = {}
|
||||
# Track which prev keys are touched this tick so stale entries can be evicted.
|
||||
seen: set = set()
|
||||
|
||||
for it in items:
|
||||
if it.get("status") != "running":
|
||||
@@ -76,6 +78,7 @@ def collect_once(db, items, clients, prev, _now=None):
|
||||
iops_write=rate("wr_ops") if inst_type == "qemu" else None,
|
||||
))
|
||||
prev[key] = (now, cur)
|
||||
seen.add(key)
|
||||
|
||||
# per-NIC
|
||||
ck = (sid, node)
|
||||
@@ -93,9 +96,15 @@ def collect_once(db, items, clients, prev, _now=None):
|
||||
out_rate=compute_rate(npc.get("out"), float(dout), ndt),
|
||||
))
|
||||
prev[nkey] = (now, {"in": float(din), "out": float(dout)})
|
||||
seen.add(nkey)
|
||||
except Exception as e:
|
||||
logger.debug(f"[metrics_collector] {sid}:{vmid} error: {e}")
|
||||
|
||||
# Evict stale keys from prev (VMs/NICs not seen this tick).
|
||||
for k in list(prev):
|
||||
if k not in seen:
|
||||
del prev[k]
|
||||
|
||||
|
||||
async def metrics_collector_loop(db_factory) -> None:
|
||||
from ..config import settings
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from datetime import timezone as _tz
|
||||
|
||||
_RD_OPS = re.compile(r"rd_operations=(\d+)")
|
||||
_WR_OPS = re.compile(r"wr_operations=(\d+)")
|
||||
@@ -93,8 +94,6 @@ def aggregate_series(rows, from_ts, bucket, fields):
|
||||
return out
|
||||
|
||||
|
||||
from datetime import timezone as _tz
|
||||
|
||||
_AGG_FIELDS = ["cpu", "mem", "maxmem", "netin", "netout",
|
||||
"diskread", "diskwrite", "diskpct", "iops_read", "iops_write"]
|
||||
|
||||
@@ -119,16 +118,24 @@ def query_instance_metrics(db, server_id, vmid, from_ts, to_ts, nic="all"):
|
||||
.order_by(InstanceMetric.ts.asc()).all())
|
||||
|
||||
# Distinct NICs seen in the window (for the selector).
|
||||
nic_rows = (db.query(InstanceNicMetric)
|
||||
.filter(InstanceNicMetric.server_id == server_id,
|
||||
InstanceNicMetric.vmid == vmid,
|
||||
InstanceNicMetric.ts >= start, InstanceNicMetric.ts <= end)
|
||||
.order_by(InstanceNicMetric.ts.asc()).all())
|
||||
nics = sorted({r.dev for r in nic_rows})
|
||||
|
||||
# Optional per-NIC override of net rates, keyed by exact timestamp.
|
||||
nic_by_ts: dict[int, tuple] = {}
|
||||
if nic != "all":
|
||||
if nic == "all":
|
||||
# Only fetch distinct dev names — avoids loading all NIC rows.
|
||||
dev_rows = (db.query(InstanceNicMetric.dev)
|
||||
.filter(InstanceNicMetric.server_id == server_id,
|
||||
InstanceNicMetric.vmid == vmid,
|
||||
InstanceNicMetric.ts >= start, InstanceNicMetric.ts <= end)
|
||||
.distinct().all())
|
||||
nics = sorted({r.dev for r in dev_rows})
|
||||
nic_by_ts: dict[int, tuple] = {}
|
||||
else:
|
||||
# Load full NIC rows for the requested dev to build per-ts override map.
|
||||
nic_rows = (db.query(InstanceNicMetric)
|
||||
.filter(InstanceNicMetric.server_id == server_id,
|
||||
InstanceNicMetric.vmid == vmid,
|
||||
InstanceNicMetric.ts >= start, InstanceNicMetric.ts <= end)
|
||||
.order_by(InstanceNicMetric.ts.asc()).all())
|
||||
nics = sorted({r.dev for r in nic_rows})
|
||||
nic_by_ts = {}
|
||||
for r in nic_rows:
|
||||
if r.dev == nic:
|
||||
nic_by_ts[_to_ts(r.ts)] = (r.in_rate, r.out_rate)
|
||||
|
||||
Reference in New Issue
Block a user