Skip to content

Commit 7422d2d

Browse files
Improve top_processes module: non-blocking, cached, accurate CPU stats
- Reimplemented top_processes.py to avoid blocking the agent loop - First call runs synchronously to ensure data is available immediately - Subsequent updates run in a background thread every 30s - CPU usage measured accurately using 1s interval (htop-like) - Maintains full compatibility with existing agent integration - Added version and date header for clarity and tracking
1 parent cd98331 commit 7422d2d

File tree

2 files changed

+50
-31
lines changed

2 files changed

+50
-31
lines changed

metrics/top_processes.py

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,70 @@
11
# ------------------------------------------------------------------------------
2-
# NodeSniff Agent - Lightweight Linux metrics collector
2+
# NodeSniff Agent - top 10 CPU-consuming processes (non-blocking, cached)
33
#
44
# Author: Sebastian Zieba <sebastian@zieba.art>
55
# License: GNU GPL v3 (non-commercial use only)
6-
#
7-
# This software is licensed under the terms of the GNU General Public License
8-
# version 3 (GPLv3) as published by the Free Software Foundation, **for
9-
# non-commercial use only**.
10-
#
11-
# For commercial licensing, please contact the author directly.
6+
# Version: 1.1.1
7+
# Date: 2025-06-27
128
# ------------------------------------------------------------------------------
139

1410
import psutil
11+
import threading
1512
import time
1613

17-
def get():
14+
_cached_processes = []
15+
_last_update = 0
16+
_update_interval = 30 # seconds
17+
_first_run = True # one-time blocking priming
18+
19+
def _update_cache():
20+
global _cached_processes, _last_update
21+
22+
processes = []
1823
try:
19-
# Priming: For accurate CPU usage, call cpu_percent(interval=None) on all processes first.
20-
for proc in psutil.process_iter():
24+
primed = []
25+
for proc in psutil.process_iter(['pid', 'name']):
2126
try:
2227
proc.cpu_percent(interval=None)
23-
except Exception:
24-
continue # Ignore processes that no longer exist
28+
primed.append(proc)
29+
except (psutil.NoSuchProcess, psutil.AccessDenied):
30+
continue
2531

26-
# Short delay to allow psutil to calculate CPU percent over an interval
27-
time.sleep(0.1)
32+
time.sleep(1.0) # wait to measure actual CPU usage
2833

29-
processes = []
30-
# Gather process info including CPU percent and memory usage
31-
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_info']):
34+
for proc in primed:
3235
try:
33-
info = proc.info
34-
memory_rss = info.get('memory_info').rss if info.get('memory_info') else 0
36+
info = proc.as_dict(attrs=['pid', 'name', 'cpu_percent', 'memory_info'])
37+
memory_rss = info['memory_info'].rss if info['memory_info'] else 0
3538
processes.append({
36-
"pid": info['pid'], # Process ID
37-
"name": info['name'] or "unknown", # Process name (fallback if None)
38-
"cpu": round(info['cpu_percent'], 1), # CPU usage in percent
39-
"ram": round(memory_rss / 1024 / 1024, 1) # RAM usage in MB (RSS)
39+
"pid": info['pid'],
40+
"name": info['name'] or "unknown",
41+
"cpu": round(info['cpu_percent'], 1),
42+
"ram": round(memory_rss / 1024 / 1024, 1) # MB
4043
})
4144
except (psutil.NoSuchProcess, psutil.AccessDenied):
42-
continue # Skip processes that are gone or inaccessible
45+
continue
46+
47+
_cached_processes = sorted(processes, key=lambda p: (p['cpu'], p['ram']), reverse=True)[:10]
48+
_last_update = time.time()
49+
50+
except Exception:
51+
_cached_processes = []
52+
_last_update = time.time()
53+
54+
def get():
55+
global _last_update, _first_run
56+
57+
now = time.time()
4358

44-
# Sort processes by CPU usage descending and return top 10
45-
top = sorted(processes, key=lambda p: p['cpu'], reverse=True)[:10]
46-
return {"top_cpu_processes": top}
59+
if _first_run:
60+
_update_cache() # blocking on first call to fill cache
61+
_first_run = False
62+
elif now - _last_update > _update_interval:
63+
threading.Thread(target=_update_cache, daemon=True).start()
4764

48-
except Exception as e:
49-
# If anything goes wrong, return error string in result
50-
return {"top_cpu_processes": f"Error: {str(e)}"}
65+
return {"top_cpu_processes": _cached_processes}
5166

67+
# Optional debug entry point
68+
if __name__ == "__main__":
69+
import json
70+
print(json.dumps(get(), indent=2))

nsagent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# ------------------------------------------------------------------------------
33
# NodeSniff Agent - Lightweight Linux metrics collector
44
#
5-
# Author: Sebastian Zieba <www.zieba.art>
5+
# Author: www.nodesniff.com
66
# License: GNU GPL v3 (non-commercial use only)
77
#
88
# This software is licensed under the terms of the GNU General Public License

0 commit comments

Comments
 (0)