monitorify 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- monitorify/__init__.py +3 -0
- monitorify/__main__.py +4 -0
- monitorify/collector/__init__.py +5 -0
- monitorify/collector/manager.py +48 -0
- monitorify/collector/schema.py +22 -0
- monitorify/collector/worker.py +44 -0
- monitorify/config.py +26 -0
- monitorify/daemon.py +32 -0
- monitorify/main.py +53 -0
- monitorify/stats/DiskStats.py +108 -0
- monitorify/stats/__init__.py +1 -0
- monitorify/stats/cpuStats.py +46 -0
- monitorify/stats/networkStats.py +69 -0
- monitorify/stats/programmList.py +272 -0
- monitorify/stats/ramStats.py +18 -0
- monitorify/storage/__init__.py +3 -0
- monitorify/storage/db.py +154 -0
- monitorify/ui/__init__.py +1 -0
- monitorify/ui/tui/__init__.py +1 -0
- monitorify/ui/tui/components/menu.py +180 -0
- monitorify/ui/tui/components/widgetManager.py +43 -0
- monitorify/ui/tui/css/tui.css +168 -0
- monitorify/ui/tui/tui.py +187 -0
- monitorify/ui/tui/widgets/__init__.py +1 -0
- monitorify/ui/tui/widgets/brailleGraph.py +261 -0
- monitorify/ui/tui/widgets/cpuWidget.py +46 -0
- monitorify/ui/tui/widgets/diskWidget.py +207 -0
- monitorify/ui/tui/widgets/networkWidget.py +98 -0
- monitorify/ui/tui/widgets/procInfoWidget.py +44 -0
- monitorify/ui/tui/widgets/programmListWidget.py +202 -0
- monitorify/ui/tui/widgets/ramWidget.py +36 -0
- monitorify/ui/tui/widgets/statusWidget.py +45 -0
- monitorify-1.0.0.dist-info/METADATA +135 -0
- monitorify-1.0.0.dist-info/RECORD +37 -0
- monitorify-1.0.0.dist-info/WHEEL +5 -0
- monitorify-1.0.0.dist-info/entry_points.txt +3 -0
- monitorify-1.0.0.dist-info/top_level.txt +1 -0
monitorify/__init__.py
ADDED
monitorify/__main__.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import List, Optional, Any, Callable
|
|
2
|
+
from monitorify.collector.worker import Worker
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Manager:
|
|
6
|
+
def __init__(self, db: Optional[Any] = None, interval: float = 1.0):
|
|
7
|
+
self._workers: List[Worker] = []
|
|
8
|
+
self._interval = interval
|
|
9
|
+
if db is None:
|
|
10
|
+
from monitorify.storage.db import Database
|
|
11
|
+
self._db = Database()
|
|
12
|
+
else:
|
|
13
|
+
self._db = db
|
|
14
|
+
self._is_running = False
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def is_running(self) -> bool:
|
|
18
|
+
return self._is_running
|
|
19
|
+
|
|
20
|
+
def add_worker(self, worker: Worker):
|
|
21
|
+
self._workers.append(worker)
|
|
22
|
+
|
|
23
|
+
def setup_default_worker(self) -> Worker:
|
|
24
|
+
"""Create and register default metric worker connected to Database."""
|
|
25
|
+
worker = Worker(interval=self._interval, on_data=self._db.save)
|
|
26
|
+
self.add_worker(worker)
|
|
27
|
+
return worker
|
|
28
|
+
|
|
29
|
+
def start(self):
|
|
30
|
+
if self._is_running:
|
|
31
|
+
return
|
|
32
|
+
self._db.connect()
|
|
33
|
+
self._is_running = True
|
|
34
|
+
if not self._workers:
|
|
35
|
+
self.setup_default_worker()
|
|
36
|
+
for worker in self._workers:
|
|
37
|
+
worker.start()
|
|
38
|
+
|
|
39
|
+
def stop(self):
|
|
40
|
+
if not self._is_running:
|
|
41
|
+
return
|
|
42
|
+
for worker in self._workers:
|
|
43
|
+
worker.stop()
|
|
44
|
+
self._db.disconnect()
|
|
45
|
+
self._is_running = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from dataclasses import dataclass, asdict
|
|
2
|
+
from typing import Dict, Any, Tuple, Optional
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class NetworkMetrics:
|
|
6
|
+
rx_bytes: float
|
|
7
|
+
tx_bytes: float
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class MetricSnapshot:
|
|
11
|
+
timestamp: float
|
|
12
|
+
cpu: float
|
|
13
|
+
ram: float
|
|
14
|
+
network: Tuple[float, float]
|
|
15
|
+
processes_count: int
|
|
16
|
+
disk_io: Optional[Dict[str, Any]] = None
|
|
17
|
+
disk_storage: Optional[Dict[str, Any]] = None
|
|
18
|
+
|
|
19
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
20
|
+
"""Converts the dataclass object into a standard dictionary."""
|
|
21
|
+
return asdict(self)
|
|
22
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from threading import Thread
|
|
3
|
+
from monitorify.stats import cpuStats, ramStats, networkStats, programmList, DiskStats
|
|
4
|
+
from monitorify.collector.schema import MetricSnapshot
|
|
5
|
+
|
|
6
|
+
from typing import Callable, Optional
|
|
7
|
+
|
|
8
|
+
class Worker:
|
|
9
|
+
def __init__(self, interval: float = 1.0, on_data: Optional[Callable[[MetricSnapshot], None]] = None):
|
|
10
|
+
self._interval = interval
|
|
11
|
+
self._on_data = on_data
|
|
12
|
+
self._running = False
|
|
13
|
+
self._thread = None
|
|
14
|
+
|
|
15
|
+
def start(self):
|
|
16
|
+
self._running = True
|
|
17
|
+
self._thread = Thread(target=self._worker_loop)
|
|
18
|
+
self._thread.start()
|
|
19
|
+
|
|
20
|
+
def stop(self):
|
|
21
|
+
self._running = False
|
|
22
|
+
if self._thread:
|
|
23
|
+
self._thread.join()
|
|
24
|
+
|
|
25
|
+
def _worker_loop(self):
|
|
26
|
+
while self._running:
|
|
27
|
+
snapshot = self.collect()
|
|
28
|
+
if self._on_data:
|
|
29
|
+
try:
|
|
30
|
+
self._on_data(snapshot)
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f"Error handling metric snapshot: {e}")
|
|
33
|
+
time.sleep(self._interval)
|
|
34
|
+
|
|
35
|
+
def collect(self) -> MetricSnapshot:
|
|
36
|
+
return MetricSnapshot(
|
|
37
|
+
timestamp=time.time(),
|
|
38
|
+
cpu=cpuStats.get_cpu_usage(),
|
|
39
|
+
ram=ramStats.get_ram_usage(),
|
|
40
|
+
network=networkStats.get_network_usage(),
|
|
41
|
+
processes_count=len(programmList.get_process_list()),
|
|
42
|
+
disk_io=DiskStats.get_disk_IO(),
|
|
43
|
+
disk_storage=DiskStats.get_disk_storage(),
|
|
44
|
+
)
|
monitorify/config.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from platformdirs import user_data_dir, user_runtime_dir
|
|
4
|
+
|
|
5
|
+
# Data directory for database storage
|
|
6
|
+
DATA_DIR = Path(user_data_dir("monitorify", appauthor=False))
|
|
7
|
+
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
8
|
+
|
|
9
|
+
# Runtime directory for PID files
|
|
10
|
+
try:
|
|
11
|
+
RUNTIME_DIR = Path(user_runtime_dir("monitorify", appauthor=False))
|
|
12
|
+
RUNTIME_DIR.mkdir(parents=True, exist_ok=True)
|
|
13
|
+
except Exception:
|
|
14
|
+
RUNTIME_DIR = DATA_DIR
|
|
15
|
+
|
|
16
|
+
# Database settings
|
|
17
|
+
DB_PATH = os.getenv("MONITORIFY_DB_PATH", str(DATA_DIR / "monitorify.db"))
|
|
18
|
+
|
|
19
|
+
# Daemon PID file path
|
|
20
|
+
DAEMON_PIDFILE = Path(os.getenv("MONITORIFY_PID_PATH", str(RUNTIME_DIR / "daemon.pid")))
|
|
21
|
+
|
|
22
|
+
# Collection settings (seconds)
|
|
23
|
+
DEFAULT_COLLECTION_INTERVAL = float(os.getenv("MONITORIFY_INTERVAL", "1.0"))
|
|
24
|
+
|
|
25
|
+
# Retention settings (default: 7 days in seconds)
|
|
26
|
+
RETENTION_SECONDS = float(os.getenv("MONITORIFY_RETENTION", str(7 * 24 * 3600)))
|
monitorify/daemon.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import signal
|
|
3
|
+
import sys
|
|
4
|
+
from monitorify.collector import Manager
|
|
5
|
+
from monitorify.storage import Database
|
|
6
|
+
from monitorify import config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
print("Starting Monitorify background metrics collector daemon...")
|
|
11
|
+
db = Database(config.DB_PATH)
|
|
12
|
+
manager = Manager(db=db, interval=config.DEFAULT_COLLECTION_INTERVAL)
|
|
13
|
+
|
|
14
|
+
def signal_handler(sig, frame):
|
|
15
|
+
print("\nStopping Monitorify daemon...")
|
|
16
|
+
manager.stop()
|
|
17
|
+
sys.exit(0)
|
|
18
|
+
|
|
19
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
20
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
21
|
+
|
|
22
|
+
manager.start()
|
|
23
|
+
print(
|
|
24
|
+
f"Metrics collector running (Interval: {config.DEFAULT_COLLECTION_INTERVAL}s, DB: {config.DB_PATH}). Press Ctrl+C to stop."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
while True:
|
|
28
|
+
time.sleep(1)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
main()
|
monitorify/main.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
from monitorify import config
|
|
5
|
+
from monitorify.ui.tui.tui import MonitorifyApp
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def daemon_already_running() -> bool:
|
|
9
|
+
"""Check if a daemon process from a previous run is still alive."""
|
|
10
|
+
if not config.DAEMON_PIDFILE.exists():
|
|
11
|
+
return False
|
|
12
|
+
try:
|
|
13
|
+
pid = int(config.DAEMON_PIDFILE.read_text().strip())
|
|
14
|
+
# Signal 0 checks if the process exists without killing it
|
|
15
|
+
os.kill(pid, 0)
|
|
16
|
+
return True
|
|
17
|
+
except (ValueError, ProcessLookupError, PermissionError):
|
|
18
|
+
config.DAEMON_PIDFILE.unlink(missing_ok=True)
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def start_daemon() -> subprocess.Popen | None:
|
|
23
|
+
"""Start the daemon as a background subprocess if not already running."""
|
|
24
|
+
if daemon_already_running():
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
proc = subprocess.Popen(
|
|
28
|
+
[sys.executable, "-m", "monitorify.daemon"],
|
|
29
|
+
stdout=subprocess.DEVNULL,
|
|
30
|
+
stderr=subprocess.DEVNULL,
|
|
31
|
+
)
|
|
32
|
+
config.DAEMON_PIDFILE.write_text(str(proc.pid))
|
|
33
|
+
return proc
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def cli():
|
|
37
|
+
"""Main CLI entrypoint for Monitorify."""
|
|
38
|
+
daemon = start_daemon()
|
|
39
|
+
try:
|
|
40
|
+
app = MonitorifyApp()
|
|
41
|
+
app.run()
|
|
42
|
+
finally:
|
|
43
|
+
if daemon is not None:
|
|
44
|
+
daemon.terminate()
|
|
45
|
+
try:
|
|
46
|
+
daemon.wait(timeout=2)
|
|
47
|
+
except subprocess.TimeoutExpired:
|
|
48
|
+
daemon.kill()
|
|
49
|
+
config.DAEMON_PIDFILE.unlink(missing_ok=True)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
cli()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
def get_real_disks() -> list:
|
|
4
|
+
disks = []
|
|
5
|
+
|
|
6
|
+
with open("/proc/diskstats", "r") as f:
|
|
7
|
+
for line in f:
|
|
8
|
+
parts = line.split()
|
|
9
|
+
if len(parts) < 3:
|
|
10
|
+
continue
|
|
11
|
+
|
|
12
|
+
disk_name = parts[2]
|
|
13
|
+
# Check if it is a real physical disk (not a partition or virtual device)
|
|
14
|
+
if not os.path.exists(f"/sys/block/{disk_name}/device"):
|
|
15
|
+
continue
|
|
16
|
+
|
|
17
|
+
disks.append(disk_name)
|
|
18
|
+
|
|
19
|
+
return disks
|
|
20
|
+
|
|
21
|
+
def get_disk_IO(disks: list = None) -> dict:
|
|
22
|
+
if disks is None:
|
|
23
|
+
disks = get_real_disks()
|
|
24
|
+
|
|
25
|
+
disk_io = {}
|
|
26
|
+
|
|
27
|
+
with open("/proc/diskstats", "r") as f:
|
|
28
|
+
for line in f:
|
|
29
|
+
parts = line.split()
|
|
30
|
+
if len(parts) < 14:
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
disk_name = parts[2]
|
|
34
|
+
if disk_name in disks:
|
|
35
|
+
# Sector size in Linux is 512 bytes
|
|
36
|
+
read_bytes = int(parts[5]) * 512
|
|
37
|
+
write_bytes = int(parts[9]) * 512
|
|
38
|
+
disk_io[disk_name] = {
|
|
39
|
+
"read_bytes": read_bytes,
|
|
40
|
+
"write_bytes": write_bytes,
|
|
41
|
+
"reads": int(parts[3]),
|
|
42
|
+
"writes": int(parts[7]),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return disk_io
|
|
46
|
+
|
|
47
|
+
def get_disk_storage(disks: list = None) -> dict:
|
|
48
|
+
if disks is None:
|
|
49
|
+
disks = get_real_disks()
|
|
50
|
+
|
|
51
|
+
disk_storage = {}
|
|
52
|
+
|
|
53
|
+
# Map mount points for each device partition
|
|
54
|
+
mounts = {}
|
|
55
|
+
if os.path.exists("/proc/mounts"):
|
|
56
|
+
with open("/proc/mounts", "r") as f:
|
|
57
|
+
for line in f:
|
|
58
|
+
parts = line.split()
|
|
59
|
+
if len(parts) >= 2 and parts[0].startswith("/dev/"):
|
|
60
|
+
mounts.setdefault(parts[0], parts[1])
|
|
61
|
+
|
|
62
|
+
for disk in disks:
|
|
63
|
+
# Read total raw hardware capacity from /sys/block/<disk>/size (512-byte sectors)
|
|
64
|
+
total_hw = 0
|
|
65
|
+
size_file = f"/sys/block/{disk}/size"
|
|
66
|
+
if os.path.exists(size_file):
|
|
67
|
+
try:
|
|
68
|
+
with open(size_file, "r") as f:
|
|
69
|
+
total_hw = int(f.read().strip()) * 512
|
|
70
|
+
except (ValueError, IOError):
|
|
71
|
+
total_hw = 0
|
|
72
|
+
|
|
73
|
+
total_fs = 0
|
|
74
|
+
used_fs = 0
|
|
75
|
+
free_fs = 0
|
|
76
|
+
|
|
77
|
+
for dev_path, mount_point in mounts.items():
|
|
78
|
+
dev_name = os.path.basename(dev_path)
|
|
79
|
+
# Match disk or its partitions (e.g. sda1 -> sda, nvme0n1p1 -> nvme0n1)
|
|
80
|
+
if dev_name == disk or dev_name.startswith(disk):
|
|
81
|
+
try:
|
|
82
|
+
st = os.statvfs(mount_point)
|
|
83
|
+
total_fs += st.f_blocks * st.f_frsize
|
|
84
|
+
free_fs += st.f_bavail * st.f_frsize
|
|
85
|
+
used_fs += (st.f_blocks - st.f_bfree) * st.f_frsize
|
|
86
|
+
except OSError:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
total = total_fs if total_fs > 0 else total_hw
|
|
90
|
+
used = used_fs
|
|
91
|
+
free = free_fs if total_fs > 0 else max(0, total_hw - used_fs)
|
|
92
|
+
|
|
93
|
+
disk_storage[disk] = {
|
|
94
|
+
"total": total,
|
|
95
|
+
"used": used,
|
|
96
|
+
"free": free,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return disk_storage
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
disks = get_real_disks()
|
|
103
|
+
disk_IO = get_disk_IO(disks)
|
|
104
|
+
disk_storage = get_disk_storage(disks)
|
|
105
|
+
print("Disks:", disks)
|
|
106
|
+
print("Disk IO:", disk_IO)
|
|
107
|
+
print("Disk Storage:", disk_storage)
|
|
108
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package init
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
prev_cpu = None
|
|
4
|
+
|
|
5
|
+
def _read_cpu_times():
|
|
6
|
+
with open("/proc/stat", "r") as f:
|
|
7
|
+
line = f.readline()
|
|
8
|
+
parts = [float(x) for x in line.split()[1:]]
|
|
9
|
+
idle_time = parts[3] + parts[4] # idle + iowait
|
|
10
|
+
total_time = sum(parts)
|
|
11
|
+
return idle_time, total_time
|
|
12
|
+
|
|
13
|
+
def get_cpu_usage(interval=None, unit="percent"):
|
|
14
|
+
"""Calculate total CPU usage via /proc/stat."""
|
|
15
|
+
global prev_cpu
|
|
16
|
+
|
|
17
|
+
if prev_cpu is None:
|
|
18
|
+
t1_idle, t1_total = _read_cpu_times()
|
|
19
|
+
time.sleep(interval if interval and interval > 0 else 0.1)
|
|
20
|
+
t2_idle, t2_total = _read_cpu_times()
|
|
21
|
+
prev_cpu = (t2_idle, t2_total)
|
|
22
|
+
diff_total = t2_total - t1_total
|
|
23
|
+
diff_idle = t2_idle - t1_idle
|
|
24
|
+
elif interval is not None and interval > 0:
|
|
25
|
+
t1_idle, t1_total = _read_cpu_times()
|
|
26
|
+
time.sleep(interval)
|
|
27
|
+
t2_idle, t2_total = _read_cpu_times()
|
|
28
|
+
prev_cpu = (t2_idle, t2_total)
|
|
29
|
+
diff_total = t2_total - t1_total
|
|
30
|
+
diff_idle = t2_idle - t1_idle
|
|
31
|
+
else:
|
|
32
|
+
t2_idle, t2_total = _read_cpu_times()
|
|
33
|
+
prev_idle, prev_total = prev_cpu
|
|
34
|
+
prev_cpu = (t2_idle, t2_total)
|
|
35
|
+
diff_total = t2_total - prev_total
|
|
36
|
+
diff_idle = t2_idle - prev_idle
|
|
37
|
+
|
|
38
|
+
if diff_total == 0:
|
|
39
|
+
return 0.0
|
|
40
|
+
# Return requested unit
|
|
41
|
+
if unit == "percent":
|
|
42
|
+
return round((1.0 - (diff_idle / diff_total)) * 100, 2)
|
|
43
|
+
elif unit == "raw":
|
|
44
|
+
return 1.0 - (diff_idle / diff_total)
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError("Invalid unit. Use 'percent' or 'raw'.")
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
prev_net = None
|
|
4
|
+
|
|
5
|
+
def _read_net_dev():
|
|
6
|
+
with open("/proc/net/dev", "r") as f:
|
|
7
|
+
next(f) # Skip header
|
|
8
|
+
next(f)
|
|
9
|
+
net_stats = {}
|
|
10
|
+
for line in f:
|
|
11
|
+
parts = line.split()
|
|
12
|
+
interface = parts[0].strip(":")
|
|
13
|
+
|
|
14
|
+
rx_bytes = int(parts[1])
|
|
15
|
+
|
|
16
|
+
tx_bytes = int(parts[9])
|
|
17
|
+
|
|
18
|
+
net_stats[interface] = (rx_bytes, tx_bytes)
|
|
19
|
+
|
|
20
|
+
return net_stats
|
|
21
|
+
|
|
22
|
+
def get_default_interface():
|
|
23
|
+
with open("/proc/net/dev", "r") as f:
|
|
24
|
+
next(f) # Skip header
|
|
25
|
+
next(f)
|
|
26
|
+
for line in f:
|
|
27
|
+
parts = line.split()
|
|
28
|
+
interface = parts[0].strip(":")
|
|
29
|
+
if interface != "lo":
|
|
30
|
+
return interface
|
|
31
|
+
|
|
32
|
+
def get_network_usage(interface=None, interval=1, max_speed=None):
|
|
33
|
+
"""Set max_speed as bytes per seconds"""
|
|
34
|
+
global prev_net
|
|
35
|
+
|
|
36
|
+
if callable(interface):
|
|
37
|
+
interface = interface()
|
|
38
|
+
elif interface is None:
|
|
39
|
+
interface = get_default_interface()
|
|
40
|
+
|
|
41
|
+
if prev_net is None:
|
|
42
|
+
prev_net = _read_net_dev()
|
|
43
|
+
time.sleep(interval if interval and interval > 0 else 1)
|
|
44
|
+
current_net = _read_net_dev()
|
|
45
|
+
else:
|
|
46
|
+
current_net = _read_net_dev()
|
|
47
|
+
|
|
48
|
+
prev_rx, prev_tx = prev_net.get(interface, (0, 0))
|
|
49
|
+
current_rx, current_tx = current_net.get(interface, (0, 0))
|
|
50
|
+
|
|
51
|
+
prev_net = current_net
|
|
52
|
+
|
|
53
|
+
diff_rx = max(0, current_rx - prev_rx)
|
|
54
|
+
diff_tx = max(0, current_tx - prev_tx)
|
|
55
|
+
|
|
56
|
+
if interval <= 0:
|
|
57
|
+
interval = 1
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Return in bytes per second
|
|
61
|
+
rx_bps = diff_rx / interval
|
|
62
|
+
tx_bps = diff_tx / interval
|
|
63
|
+
return rx_bps, tx_bps
|
|
64
|
+
|
|
65
|
+
def get_total_network_stats():
|
|
66
|
+
net_stats = _read_net_dev()
|
|
67
|
+
total_rx = sum(rx for rx, tx in net_stats.values())
|
|
68
|
+
total_tx = sum(tx for rx, tx in net_stats.values())
|
|
69
|
+
return total_rx, total_tx
|