From 76f74201e5ce71927ee84d6c643861f58e141bb2 Mon Sep 17 00:00:00 2001 From: Hidosi Date: Fri, 17 Jul 2026 17:10:47 +0700 Subject: [PATCH] Add RAM usage in GB and swap metrics to agent, server, UI and CSV export --- agent-deb/DEBIAN/control | 2 +- agent-deb/opt/info-user-agent/agent.py | 55 ++++++++++++++--- agent/agent.py | 55 ++++++++++++++--- info-user-agent_1.0.1_all.deb | Bin 4108 -> 0 bytes info-user-agent_1.0.3_all.deb | Bin 0 -> 9800 bytes server/database.py | 37 ++++++++++-- server/models.py | 12 ++++ server/routes/api_routes.py | 22 ++++++- server/routes/web_routes.py | 12 +++- server/schemas.py | 20 +++++++ server/templates/computer.html | 79 ++++++++++++++++++++++--- server/templates/dashboard.html | 13 +++- 12 files changed, 272 insertions(+), 35 deletions(-) delete mode 100644 info-user-agent_1.0.1_all.deb create mode 100644 info-user-agent_1.0.3_all.deb diff --git a/agent-deb/DEBIAN/control b/agent-deb/DEBIAN/control index 2837c79..ad3fefd 100644 --- a/agent-deb/DEBIAN/control +++ b/agent-deb/DEBIAN/control @@ -1,5 +1,5 @@ Package: info-user-agent -Version: 1.0.1 +Version: 1.0.3 Section: admin Priority: optional Architecture: all diff --git a/agent-deb/opt/info-user-agent/agent.py b/agent-deb/opt/info-user-agent/agent.py index 247d0cd..b12e060 100755 --- a/agent-deb/opt/info-user-agent/agent.py +++ b/agent-deb/opt/info-user-agent/agent.py @@ -147,15 +147,31 @@ def get_load_avg(): return None -def get_top_processes(limit=10): +def get_top_processes(limit=10, processes=None): + """Return top processes by CPU usage. + + If ``processes`` is provided, it should be a list of psutil.Process objects + whose CPU baseline has already been established (cpu_percent called once). + The actual CPU usage is measured with the next call to cpu_percent(). + """ + if processes is None: + processes = [] + for p in psutil.process_iter(["pid", "name"]): + try: + p.cpu_percent(interval=None) + processes.append(p) + except Exception: + continue + time.sleep(1) + procs = [] - for p in psutil.process_iter(["pid", "name", "cpu_percent", "memory_percent"]): + for p in processes: try: procs.append({ - "pid": p.info["pid"], - "name": p.info["name"], - "cpu_percent": round(p.info["cpu_percent"] or 0, 1), - "ram_percent": round((p.info["memory_percent"] or 0), 1), + "pid": p.pid, + "name": p.name(), + "cpu_percent": round(p.cpu_percent(interval=None), 1), + "ram_percent": round(p.memory_percent(), 1), }) except Exception: continue @@ -166,6 +182,21 @@ def get_top_processes(limit=10): def collect(): cfg = load_config() sessions = get_active_sessions() + + # Establish per-process CPU baseline first. The 1-second interval below for + # system CPU also gives us meaningful per-process CPU percentages without + # an extra delay. + baseline_procs = [] + for p in psutil.process_iter(["pid", "name"]): + try: + p.cpu_percent(interval=None) + baseline_procs.append(p) + except Exception: + continue + + vm = psutil.virtual_memory() + sm = psutil.swap_memory() + payload = { "hostname": socket.gethostname(), "username": get_display_username(sessions), @@ -175,12 +206,18 @@ def collect(): "public_ip": get_public_ip() if cfg.get("send_public_ip") else None, "uptime_sec": get_uptime(), "cpu_percent": psutil.cpu_percent(interval=1), - "ram_percent": psutil.virtual_memory().percent, - "ram_total_gb": round(psutil.virtual_memory().total / (2**30), 1), + "ram_percent": vm.percent, + "ram_total_gb": round(vm.total / (2**30), 1), + "ram_used_gb": round(vm.used / (2**30), 1), + "ram_available_gb": round(vm.available / (2**30), 1), + "swap_total_gb": round(sm.total / (2**30), 1), + "swap_used_gb": round(sm.used / (2**30), 1), + "swap_free_gb": round(sm.free / (2**30), 1), + "swap_percent": sm.percent, "disk_info": get_disk_info(), "cpu_temp": get_cpu_temp(), "load_avg": get_load_avg(), - "processes": get_top_processes(10), + "processes": get_top_processes(10, baseline_procs), } return payload, cfg diff --git a/agent/agent.py b/agent/agent.py index 247d0cd..b12e060 100644 --- a/agent/agent.py +++ b/agent/agent.py @@ -147,15 +147,31 @@ def get_load_avg(): return None -def get_top_processes(limit=10): +def get_top_processes(limit=10, processes=None): + """Return top processes by CPU usage. + + If ``processes`` is provided, it should be a list of psutil.Process objects + whose CPU baseline has already been established (cpu_percent called once). + The actual CPU usage is measured with the next call to cpu_percent(). + """ + if processes is None: + processes = [] + for p in psutil.process_iter(["pid", "name"]): + try: + p.cpu_percent(interval=None) + processes.append(p) + except Exception: + continue + time.sleep(1) + procs = [] - for p in psutil.process_iter(["pid", "name", "cpu_percent", "memory_percent"]): + for p in processes: try: procs.append({ - "pid": p.info["pid"], - "name": p.info["name"], - "cpu_percent": round(p.info["cpu_percent"] or 0, 1), - "ram_percent": round((p.info["memory_percent"] or 0), 1), + "pid": p.pid, + "name": p.name(), + "cpu_percent": round(p.cpu_percent(interval=None), 1), + "ram_percent": round(p.memory_percent(), 1), }) except Exception: continue @@ -166,6 +182,21 @@ def get_top_processes(limit=10): def collect(): cfg = load_config() sessions = get_active_sessions() + + # Establish per-process CPU baseline first. The 1-second interval below for + # system CPU also gives us meaningful per-process CPU percentages without + # an extra delay. + baseline_procs = [] + for p in psutil.process_iter(["pid", "name"]): + try: + p.cpu_percent(interval=None) + baseline_procs.append(p) + except Exception: + continue + + vm = psutil.virtual_memory() + sm = psutil.swap_memory() + payload = { "hostname": socket.gethostname(), "username": get_display_username(sessions), @@ -175,12 +206,18 @@ def collect(): "public_ip": get_public_ip() if cfg.get("send_public_ip") else None, "uptime_sec": get_uptime(), "cpu_percent": psutil.cpu_percent(interval=1), - "ram_percent": psutil.virtual_memory().percent, - "ram_total_gb": round(psutil.virtual_memory().total / (2**30), 1), + "ram_percent": vm.percent, + "ram_total_gb": round(vm.total / (2**30), 1), + "ram_used_gb": round(vm.used / (2**30), 1), + "ram_available_gb": round(vm.available / (2**30), 1), + "swap_total_gb": round(sm.total / (2**30), 1), + "swap_used_gb": round(sm.used / (2**30), 1), + "swap_free_gb": round(sm.free / (2**30), 1), + "swap_percent": sm.percent, "disk_info": get_disk_info(), "cpu_temp": get_cpu_temp(), "load_avg": get_load_avg(), - "processes": get_top_processes(10), + "processes": get_top_processes(10, baseline_procs), } return payload, cfg diff --git a/info-user-agent_1.0.1_all.deb b/info-user-agent_1.0.1_all.deb deleted file mode 100644 index 59153071df9e29294cc732dd662a873b865c2b32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4108 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 diff --git a/info-user-agent_1.0.3_all.deb b/info-user-agent_1.0.3_all.deb new file mode 100644 index 0000000000000000000000000000000000000000..9d21a9a7e79d6ed482c89c99c78870f5321d6d6b GIT binary patch literal 9800 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 +
+
+
+
+
RAM
+
+ {% if computer.current_ram_used_gb is not none and computer.current_ram_total_gb is not none %} + {{ computer.current_ram_used_gb | round(1) }} / {{ computer.current_ram_total_gb | round(1) }} GB + ({{ computer.current_ram_percent | round(1) if computer.current_ram_percent is not none else '-' }}%) + {% else %} + {{ computer.current_ram_percent | round(1) if computer.current_ram_percent is not none else '-' }}% + {% endif %} +
+ {% if computer.current_ram_available_gb is not none %} +
доступно {{ computer.current_ram_available_gb | round(1) }} GB
+ {% endif %} +
+
+
+
+
+
+
Swap
+
+ {% if computer.current_swap_used_gb is not none and computer.current_swap_total_gb is not none %} + {{ computer.current_swap_used_gb | round(1) }} / {{ computer.current_swap_total_gb | round(1) }} GB + ({{ computer.current_swap_percent | round(1) if computer.current_swap_percent is not none else '-' }}%) + {% else %} + {{ computer.current_swap_percent | round(1) if computer.current_swap_percent is not none else '-' }}% + {% endif %} +
+ {% if computer.current_swap_free_gb is not none %} +
свободно {{ computer.current_swap_free_gb | round(1) }} GB
+ {% endif %} +
+
+
+
+
Активные сессии
@@ -75,7 +114,7 @@
-
+
CPU, %
@@ -83,7 +122,7 @@
-
+
RAM, %
@@ -91,6 +130,14 @@
+
+
+
+
Swap, %
+ +
+
+
@@ -120,13 +167,21 @@
История heartbeats
- + {% for h in heartbeats[-20:] | reverse %} + + {% endfor %} @@ -135,22 +190,32 @@ +{% endblock %} + +{% block scripts %} {% endblock %} diff --git a/server/templates/dashboard.html b/server/templates/dashboard.html index b6734b2..33db776 100644 --- a/server/templates/dashboard.html +++ b/server/templates/dashboard.html @@ -59,6 +59,7 @@ + @@ -84,7 +85,14 @@ {% endif %} - + + - + +
ВремяCPURAMIP
ВремяCPURAMRAM GBSwapIP
{{ h.timestamp[:16] | replace('T', ' ') if h.timestamp else '-' }} {{ h.cpu_percent | round(1) }}% {{ h.ram_percent | round(1) }}% + {% if h.ram_used_gb is not none and h.ram_total_gb is not none %} + {{ h.ram_used_gb | round(1) }} / {{ h.ram_total_gb | round(1) }} + {% else %} + - + {% endif %} + {{ h.swap_percent | round(1) if h.swap_percent is not none else '-' }}% {{ h.local_ip or '-' }}
Статус CPU RAMSwap OS Последний чек {{ c.current_cpu_percent | round(1) if c.current_cpu_percent is not none else '-' }}%{{ c.current_ram_percent | round(1) if c.current_ram_percent is not none else '-' }}% + {% if c.current_ram_used_gb is not none and c.current_ram_total_gb is not none %} + {{ c.current_ram_used_gb | round(1) }} / {{ c.current_ram_total_gb | round(1) }} GB + {% else %} + {{ c.current_ram_percent | round(1) if c.current_ram_percent is not none else '-' }}% + {% endif %} + {{ c.current_swap_percent | round(1) if c.current_swap_percent is not none else '-' }}% {{ c.os_info or '-' }} {{ c.last_seen | localtime }} @@ -187,7 +195,8 @@ async function loadDashboard() { ${escapeHtml(c.current_ip || '-')} ${c.status === 'online' ? 'Online' : 'Offline'} ${c.current_cpu_percent != null ? c.current_cpu_percent.toFixed(1) : '-'}%${c.current_ram_percent != null ? c.current_ram_percent.toFixed(1) : '-'}%${c.current_ram_used_gb != null && c.current_ram_total_gb != null ? `${c.current_ram_used_gb.toFixed(1)} / ${c.current_ram_total_gb.toFixed(1)} GB` : (c.current_ram_percent != null ? c.current_ram_percent.toFixed(1) + '%' : '-')}${c.current_swap_percent != null ? c.current_swap_percent.toFixed(1) : '-'}% ${escapeHtml(c.os_info || '-')} ${formatServerTime(c.last_seen)}