Files
jardin/backend/app/routers/settings.py
2026-02-22 18:34:50 +01:00

164 lines
4.9 KiB
Python

import os
import shutil
import time
from typing import Any
from fastapi import APIRouter, Depends
from sqlmodel import Session, select
from app.database import get_session
from app.models.settings import UserSettings
from app.config import UPLOAD_DIR
router = APIRouter(tags=["réglages"])
_PREV_CPU_USAGE_USEC: int | None = None
_PREV_CPU_TS: float | None = None
def _read_int_from_paths(paths: list[str]) -> int | None:
for path in paths:
try:
with open(path, "r", encoding="utf-8") as f:
raw = f.read().strip().split()[0]
return int(raw)
except Exception:
continue
return None
def _read_cgroup_cpu_usage_usec() -> int | None:
# cgroup v2
try:
with open("/sys/fs/cgroup/cpu.stat", "r", encoding="utf-8") as f:
for line in f:
if line.startswith("usage_usec "):
return int(line.split()[1])
except Exception:
pass
# cgroup v1
ns = _read_int_from_paths(["/sys/fs/cgroup/cpuacct/cpuacct.usage"])
if ns is not None:
return ns // 1000
return None
def _cpu_quota_cores() -> float | None:
# cgroup v2
try:
with open("/sys/fs/cgroup/cpu.max", "r", encoding="utf-8") as f:
quota, period = f.read().strip().split()[:2]
if quota == "max":
return float(os.cpu_count() or 1)
q, p = int(quota), int(period)
if p > 0:
return max(q / p, 0.01)
except Exception:
pass
# cgroup v1
quota = _read_int_from_paths(["/sys/fs/cgroup/cpu/cpu.cfs_quota_us"])
period = _read_int_from_paths(["/sys/fs/cgroup/cpu/cpu.cfs_period_us"])
if quota is not None and period is not None and quota > 0 and period > 0:
return max(quota / period, 0.01)
return float(os.cpu_count() or 1)
def _memory_stats() -> dict[str, Any]:
used = _read_int_from_paths(
[
"/sys/fs/cgroup/memory.current", # cgroup v2
"/sys/fs/cgroup/memory/memory.usage_in_bytes", # cgroup v1
]
)
limit = _read_int_from_paths(
[
"/sys/fs/cgroup/memory.max", # cgroup v2
"/sys/fs/cgroup/memory/memory.limit_in_bytes", # cgroup v1
]
)
# Certaines limites cgroup valent "max" ou des sentinelles tres grandes.
if limit is not None and limit >= 9_000_000_000_000_000_000:
limit = None
pct = None
if used is not None and limit and limit > 0:
pct = round((used / limit) * 100, 1)
return {"used_bytes": used, "limit_bytes": limit, "used_pct": pct}
def _disk_stats() -> dict[str, Any]:
target = "/data" if os.path.isdir("/data") else "/"
total, used, free = shutil.disk_usage(target)
uploads_size = None
if os.path.isdir(UPLOAD_DIR):
try:
uploads_size = sum(
os.path.getsize(os.path.join(root, name))
for root, _, files in os.walk(UPLOAD_DIR)
for name in files
)
except Exception:
uploads_size = None
return {
"path": target,
"total_bytes": total,
"used_bytes": used,
"free_bytes": free,
"used_pct": round((used / total) * 100, 1) if total else None,
"uploads_bytes": uploads_size,
}
@router.get("/settings")
def get_settings(session: Session = Depends(get_session)):
rows = session.exec(select(UserSettings)).all()
return {r.cle: r.valeur for r in rows}
@router.put("/settings")
def update_settings(data: dict, session: Session = Depends(get_session)):
for cle, valeur in data.items():
row = session.exec(select(UserSettings).where(UserSettings.cle == cle)).first()
if row:
row.valeur = str(valeur)
else:
row = UserSettings(cle=cle, valeur=str(valeur))
session.add(row)
session.commit()
return {"ok": True}
@router.get("/settings/debug/system")
def get_debug_system_stats() -> dict[str, Any]:
"""Stats runtime du conteneur (utile pour affichage debug UI)."""
global _PREV_CPU_USAGE_USEC, _PREV_CPU_TS
now = time.monotonic()
usage_usec = _read_cgroup_cpu_usage_usec()
quota_cores = _cpu_quota_cores()
cpu_pct = None
if usage_usec is not None and _PREV_CPU_USAGE_USEC is not None and _PREV_CPU_TS is not None:
delta_usage = usage_usec - _PREV_CPU_USAGE_USEC
delta_time_usec = (now - _PREV_CPU_TS) * 1_000_000
if delta_time_usec > 0 and quota_cores and quota_cores > 0:
cpu_pct = round((delta_usage / (delta_time_usec * quota_cores)) * 100, 1)
_PREV_CPU_USAGE_USEC = usage_usec
_PREV_CPU_TS = now
return {
"source": "container-cgroup",
"cpu": {
"usage_usec_total": usage_usec,
"quota_cores": quota_cores,
"used_pct": cpu_pct,
},
"memory": _memory_stats(),
"disk": _disk_stats(),
}