feat: документация из Nextcloud, график у сотрудника, автоочистка фото, мониторинг, итог мастера, фильтр по местам; доп. сервисы в docker-compose
- docker-compose.yml: профили docs (Nextcloud), office (ONLYOFFICE Document Server), ai (Ollama + скачивание модели) - автонастройка: адреса Nextcloud, ONLYOFFICE, портала и модели подставляются из .env или по именам сервисов во внутренней сети Docker; значения из интерфейса важнее - локальная модель в своей сети (имя сервиса, частный IP) подключается без ключа API - .env.example, docs/DEPLOY.md (раздел 7а), README
This commit is contained in:
+109
@@ -0,0 +1,109 @@
|
||||
"""Мониторинг сервера: загрузка процессора, память, место на дисках. Без внешних библиотек.
|
||||
|
||||
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}
|
||||
Reference in New Issue
Block a user