164 lines
4.9 KiB
Python
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(),
|
|
}
|