- docker-compose.yml: профили docs (Nextcloud), office (ONLYOFFICE Document Server), ai (Ollama + скачивание модели) - автонастройка: адреса Nextcloud, ONLYOFFICE, портала и модели подставляются из .env или по именам сервисов во внутренней сети Docker; значения из интерфейса важнее - локальная модель в своей сети (имя сервиса, частный IP) подключается без ключа API - .env.example, docs/DEPLOY.md (раздел 7а), README
110 lines
4.3 KiB
Python
110 lines
4.3 KiB
Python
"""Мониторинг сервера: загрузка процессора, память, место на дисках. Без внешних библиотек.
|
|
|
|
Linux (в том числе Docker): /proc/stat, /proc/meminfo; если контейнеру задан лимит памяти (cgroup v2 memory.max) —
|
|
показываем память контейнера. Windows (локальный запуск): GetSystemTimes и GlobalMemoryStatusEx через ctypes."""
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
SAMPLE_SEC = 0.25
|
|
|
|
|
|
def _linux_cpu_times() -> tuple[int, int]:
|
|
with open("/proc/stat") as f:
|
|
vals = [int(x) for x in f.readline().split()[1:]]
|
|
idle = vals[3] + (vals[4] if len(vals) > 4 else 0) # idle + iowait
|
|
return idle, sum(vals)
|
|
|
|
|
|
def _win_cpu_times() -> tuple[int, int]:
|
|
import ctypes
|
|
from ctypes import wintypes
|
|
idle, kernel, user = wintypes.FILETIME(), wintypes.FILETIME(), wintypes.FILETIME()
|
|
ctypes.windll.kernel32.GetSystemTimes(ctypes.byref(idle), ctypes.byref(kernel), ctypes.byref(user))
|
|
v = lambda t: (t.dwHighDateTime << 32) | t.dwLowDateTime
|
|
return v(idle), v(kernel) + v(user) # время ядра уже включает простой
|
|
|
|
|
|
def cpu_percent() -> float | None:
|
|
get = _win_cpu_times if sys.platform == "win32" else _linux_cpu_times
|
|
try:
|
|
i1, t1 = get()
|
|
time.sleep(SAMPLE_SEC)
|
|
i2, t2 = get()
|
|
except (OSError, ValueError, AttributeError):
|
|
return None
|
|
dt = t2 - t1
|
|
return round(100.0 * (1 - (i2 - i1) / dt), 1) if dt > 0 else 0.0
|
|
|
|
|
|
def _cgroup_memory() -> tuple[int, int] | None:
|
|
try:
|
|
limit = Path("/sys/fs/cgroup/memory.max").read_text().strip()
|
|
if limit == "max":
|
|
return None
|
|
return int(Path("/sys/fs/cgroup/memory.current").read_text()), int(limit)
|
|
except (OSError, ValueError):
|
|
return None
|
|
|
|
|
|
def memory() -> dict | None:
|
|
"""{used, total, scope: host|container} в байтах."""
|
|
if sys.platform == "win32":
|
|
try:
|
|
import ctypes
|
|
|
|
class MS(ctypes.Structure):
|
|
_fields_ = [("dwLength", ctypes.c_ulong), ("dwMemoryLoad", ctypes.c_ulong), ("ullTotalPhys", ctypes.c_ulonglong),
|
|
("ullAvailPhys", ctypes.c_ulonglong), ("ullTotalPageFile", ctypes.c_ulonglong), ("ullAvailPageFile", ctypes.c_ulonglong),
|
|
("ullTotalVirtual", ctypes.c_ulonglong), ("ullAvailVirtual", ctypes.c_ulonglong), ("ullAvailExtendedVirtual", ctypes.c_ulonglong)]
|
|
m = MS()
|
|
m.dwLength = ctypes.sizeof(MS)
|
|
ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(m))
|
|
return {"used": m.ullTotalPhys - m.ullAvailPhys, "total": m.ullTotalPhys, "scope": "host"}
|
|
except (OSError, AttributeError):
|
|
return None
|
|
cg = _cgroup_memory()
|
|
if cg:
|
|
return {"used": cg[0], "total": cg[1], "scope": "container"}
|
|
try:
|
|
info = {}
|
|
with open("/proc/meminfo") as f:
|
|
for line in f:
|
|
k, v = line.split(":", 1)
|
|
info[k] = int(v.split()[0]) * 1024
|
|
total = info["MemTotal"]
|
|
return {"used": total - info.get("MemAvailable", info.get("MemFree", 0)), "total": total, "scope": "host"}
|
|
except (OSError, KeyError, ValueError):
|
|
return None
|
|
|
|
|
|
def disks(paths: dict[str, Path]) -> list[dict]:
|
|
"""Место на дисках, где лежат база, фото и т. п. Один и тот же диск показывается один раз."""
|
|
out, seen = [], {}
|
|
for label, p in paths.items():
|
|
p = Path(p)
|
|
while not p.exists() and p.parent != p:
|
|
p = p.parent
|
|
try:
|
|
u = shutil.disk_usage(p)
|
|
except OSError:
|
|
continue
|
|
key = (u.total, os.stat(p).st_dev)
|
|
if key in seen:
|
|
seen[key]["labels"].append(label)
|
|
continue
|
|
seen[key] = {"labels": [label], "path": str(p), "used": u.used, "total": u.total, "free": u.free}
|
|
out.append(seen[key])
|
|
return out
|
|
|
|
|
|
def snapshot(paths: dict[str, Path]) -> dict:
|
|
try:
|
|
load = os.getloadavg()
|
|
except (OSError, AttributeError):
|
|
load = None
|
|
return {"cpu": cpu_percent(), "cpu_count": os.cpu_count(), "load": load, "memory": memory(), "disks": disks(paths),
|
|
"platform": sys.platform}
|