mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 11:50:44 +08:00
Show netx database used size on the workbench storage gauge.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
155cb4e921
commit
938cd39c06
7 changed files with 178 additions and 2 deletions
65
netx_api/db_storage_metrics.py
Normal file
65
netx_api/db_storage_metrics.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
"""PostgreSQL database size for workbench (used bytes only)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
from .config import settings
|
||||
|
||||
_CACHE_LOCK = threading.Lock()
|
||||
_CACHED: dict[str, Any] | None = None
|
||||
_CACHED_AT = 0.0
|
||||
_CACHE_TTL_SEC = 30.0
|
||||
|
||||
|
||||
def _open_session():
|
||||
"""Indirection so tests can patch without importing the full engine."""
|
||||
from .db import SessionLocal
|
||||
|
||||
return SessionLocal()
|
||||
|
||||
|
||||
def _collect_uncached() -> dict[str, Any]:
|
||||
url = str(settings.database_url or "")
|
||||
out: dict[str, Any] = {
|
||||
"used_bytes": 0,
|
||||
"source": "none",
|
||||
}
|
||||
if url.startswith("sqlite"):
|
||||
out["source"] = "sqlite"
|
||||
out["error"] = "not_applicable"
|
||||
return out
|
||||
|
||||
try:
|
||||
with _open_session() as db:
|
||||
row = db.execute(
|
||||
text(
|
||||
"SELECT current_database() AS db_name, "
|
||||
"pg_database_size(current_database())::bigint AS used_bytes"
|
||||
)
|
||||
).mappings().one()
|
||||
out["db_name"] = str(row["db_name"] or "")
|
||||
out["used_bytes"] = int(row["used_bytes"] or 0)
|
||||
out["source"] = "pg_database_size"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
out["source"] = "error"
|
||||
out["error"] = str(exc)[:200]
|
||||
return out
|
||||
|
||||
|
||||
def collect_db_storage_metrics() -> dict[str, Any]:
|
||||
"""Return PG used-size dict; never raises. Cached ~30s."""
|
||||
global _CACHED, _CACHED_AT
|
||||
now = time.monotonic()
|
||||
with _CACHE_LOCK:
|
||||
if _CACHED is not None and (now - _CACHED_AT) < _CACHE_TTL_SEC:
|
||||
return dict(_CACHED)
|
||||
out = _collect_uncached()
|
||||
with _CACHE_LOCK:
|
||||
_CACHED = dict(out)
|
||||
_CACHED_AT = time.monotonic()
|
||||
return out
|
||||
|
|
@ -12,6 +12,7 @@ from .audit_async import audit_queue_status
|
|||
from .cli_budget import cli_budget_status
|
||||
from .config import settings
|
||||
from .db import db_pool_status
|
||||
from .db_storage_metrics import collect_db_storage_metrics
|
||||
from .host_metrics import collect_host_metrics
|
||||
from .oclaw_alarm_forwarder import forwarder_status
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ def collect_runtime_metrics() -> dict[str, Any]:
|
|||
"pid": os.getpid(),
|
||||
"thread_count": threading.active_count(),
|
||||
"db_pool": db_pool_status(),
|
||||
"db_storage": collect_db_storage_metrics(),
|
||||
"cli_budget": cli_budget_status(),
|
||||
"audit_queue": audit_queue_status(),
|
||||
"oclaw_forwarder": forwarder_status(),
|
||||
|
|
@ -142,6 +144,9 @@ def _prom_lines(metrics: dict[str, Any]) -> str:
|
|||
lines.append(f'netx_host_mem_used_bytes {host["mem_used_bytes"]}')
|
||||
if host.get("mem_total_bytes") is not None:
|
||||
lines.append(f'netx_host_mem_total_bytes {host["mem_total_bytes"]}')
|
||||
storage = metrics.get("db_storage") or {}
|
||||
if storage.get("used_bytes") is not None:
|
||||
lines.append(f'netx_db_storage_used_bytes {int(storage.get("used_bytes") or 0)}')
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue