monitorify 1.0.0__tar.gz

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.
Files changed (42) hide show
  1. monitorify-1.0.0/PKG-INFO +135 -0
  2. monitorify-1.0.0/README.md +123 -0
  3. monitorify-1.0.0/pyproject.toml +29 -0
  4. monitorify-1.0.0/setup.cfg +4 -0
  5. monitorify-1.0.0/src/monitorify/__init__.py +3 -0
  6. monitorify-1.0.0/src/monitorify/__main__.py +4 -0
  7. monitorify-1.0.0/src/monitorify/collector/__init__.py +5 -0
  8. monitorify-1.0.0/src/monitorify/collector/manager.py +48 -0
  9. monitorify-1.0.0/src/monitorify/collector/schema.py +22 -0
  10. monitorify-1.0.0/src/monitorify/collector/worker.py +44 -0
  11. monitorify-1.0.0/src/monitorify/config.py +26 -0
  12. monitorify-1.0.0/src/monitorify/daemon.py +32 -0
  13. monitorify-1.0.0/src/monitorify/main.py +53 -0
  14. monitorify-1.0.0/src/monitorify/stats/DiskStats.py +108 -0
  15. monitorify-1.0.0/src/monitorify/stats/__init__.py +1 -0
  16. monitorify-1.0.0/src/monitorify/stats/cpuStats.py +46 -0
  17. monitorify-1.0.0/src/monitorify/stats/networkStats.py +69 -0
  18. monitorify-1.0.0/src/monitorify/stats/programmList.py +272 -0
  19. monitorify-1.0.0/src/monitorify/stats/ramStats.py +18 -0
  20. monitorify-1.0.0/src/monitorify/storage/__init__.py +3 -0
  21. monitorify-1.0.0/src/monitorify/storage/db.py +154 -0
  22. monitorify-1.0.0/src/monitorify/ui/__init__.py +1 -0
  23. monitorify-1.0.0/src/monitorify/ui/tui/__init__.py +1 -0
  24. monitorify-1.0.0/src/monitorify/ui/tui/components/menu.py +180 -0
  25. monitorify-1.0.0/src/monitorify/ui/tui/components/widgetManager.py +43 -0
  26. monitorify-1.0.0/src/monitorify/ui/tui/css/tui.css +168 -0
  27. monitorify-1.0.0/src/monitorify/ui/tui/tui.py +187 -0
  28. monitorify-1.0.0/src/monitorify/ui/tui/widgets/__init__.py +1 -0
  29. monitorify-1.0.0/src/monitorify/ui/tui/widgets/brailleGraph.py +261 -0
  30. monitorify-1.0.0/src/monitorify/ui/tui/widgets/cpuWidget.py +46 -0
  31. monitorify-1.0.0/src/monitorify/ui/tui/widgets/diskWidget.py +207 -0
  32. monitorify-1.0.0/src/monitorify/ui/tui/widgets/networkWidget.py +98 -0
  33. monitorify-1.0.0/src/monitorify/ui/tui/widgets/procInfoWidget.py +44 -0
  34. monitorify-1.0.0/src/monitorify/ui/tui/widgets/programmListWidget.py +202 -0
  35. monitorify-1.0.0/src/monitorify/ui/tui/widgets/ramWidget.py +36 -0
  36. monitorify-1.0.0/src/monitorify/ui/tui/widgets/statusWidget.py +45 -0
  37. monitorify-1.0.0/src/monitorify.egg-info/PKG-INFO +135 -0
  38. monitorify-1.0.0/src/monitorify.egg-info/SOURCES.txt +40 -0
  39. monitorify-1.0.0/src/monitorify.egg-info/dependency_links.txt +1 -0
  40. monitorify-1.0.0/src/monitorify.egg-info/entry_points.txt +3 -0
  41. monitorify-1.0.0/src/monitorify.egg-info/requires.txt +3 -0
  42. monitorify-1.0.0/src/monitorify.egg-info/top_level.txt +1 -0
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: monitorify
3
+ Version: 1.0.0
4
+ Summary: A lightweight Linux terminal system monitor built with Textual
5
+ Author: Leon
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: textual>=0.80.0
10
+ Requires-Dist: platformdirs>=4.0.0
11
+ Requires-Dist: rich>=13.0.0
12
+
13
+ # Monitorify
14
+
15
+ A lightweight system monitor that runs right in your terminal. It tracks CPU usage, memory consumption, network activity, disk metrics, and process states in real time with historical graphing — no bloated dependencies, just a clean, high-performance TUI dashboard.
16
+
17
+ Built with Python and [Textual](https://github.com/Textualize/textual). Reads directly from `/proc` and `/sys` for lightning-fast, native performance on Linux.
18
+
19
+ ---
20
+
21
+ ## Features
22
+
23
+ * **CPU & System Info** — Live CPU percentage, multi-timeframe braille graphing, OS kernel, hostname, and system uptime.
24
+ * **Memory (RAM)** — Real-time memory utilization, percentage, and historical graph.
25
+ * **Network Throughput** — Split-view upload/download graph with current speed, peak throughput, and total transferred bytes.
26
+ * **Disk Metrics** — I/O activity rates (reads/writes) and storage capacity breakdown for physical disks.
27
+ * **Process Monitor** — Interactive list of running processes with CPU/RAM metrics, process state, uptime, and real-time inspector.
28
+ * **Background Collector Daemon** — Lightweight collector logging metrics to an SQLite database (`~/.local/share/monitorify/monitorify.db`) with automatic pruning.
29
+ * **Customizable Menu (`m`)** — Toggle widget visibility, adjust collection frequency, and set history graph duration (1m, 5m, 10m, 30m, 1h, or custom durations like `2h`, `1d`).
30
+
31
+ ---
32
+
33
+ ## Quick Start
34
+
35
+ ```bash
36
+ # Recommended — isolated install via pipx
37
+ pipx install git+https://github.com/leonst036/Monitorify.git
38
+
39
+ # Then launch the dashboard
40
+ monitorify
41
+ ```
42
+
43
+ > For full installation options (pip, source, systemd daemon) see **[INSTALL.md](INSTALL.md)**.
44
+
45
+ ---
46
+
47
+ ## Usage
48
+
49
+ ### Dashboard
50
+
51
+ ```bash
52
+ monitorify
53
+ ```
54
+
55
+ | Key | Action |
56
+ |-----|--------|
57
+ | `m` | Open settings menu (toggle widgets, set intervals, change history window) |
58
+ | `q` / `c` | Quit |
59
+
60
+ ### Standalone Background Daemon
61
+
62
+ ```bash
63
+ monitorify-daemon
64
+ ```
65
+
66
+ Metrics are written to `~/.local/share/monitorify/monitorify.db` continuously.
67
+
68
+ ---
69
+
70
+ ## Configuration & Paths
71
+
72
+ Monitorify follows XDG base directory conventions:
73
+
74
+ | Purpose | Default Path | Environment Variable |
75
+ |---|---|---|
76
+ | **Database** | `~/.local/share/monitorify/monitorify.db` | `MONITORIFY_DB_PATH` |
77
+ | **Daemon PID** | `/run/user/<uid>/monitorify/daemon.pid` | `MONITORIFY_PID_PATH` |
78
+ | **Interval** | `1.0` seconds | `MONITORIFY_INTERVAL` |
79
+ | **Retention** | `604800` seconds (7 days) | `MONITORIFY_RETENTION` |
80
+
81
+ ---
82
+
83
+ ## Project Structure
84
+
85
+ ```
86
+ Monitorify/
87
+ ├── pyproject.toml # Package build configuration & entry points
88
+ ├── README.md # This file
89
+ ├── INSTALL.md # Full installation guide
90
+ ├── main.py # Developer entrypoint shim
91
+ ├── src/
92
+ │ └── monitorify/
93
+ │ ├── __init__.py # Package metadata (__version__)
94
+ │ ├── __main__.py # python -m monitorify entrypoint
95
+ │ ├── config.py # XDG path handling & environment settings
96
+ │ ├── main.py # Dashboard runner & daemon supervisor
97
+ │ ├── daemon.py # Standalone metrics collector daemon
98
+ │ ├── collector/
99
+ │ │ ├── manager.py # Worker lifecycle coordinator
100
+ │ │ ├── worker.py # Background metric collection thread
101
+ │ │ └── schema.py # Dataclass metric structures
102
+ │ ├── stats/
103
+ │ │ ├── cpuStats.py # /proc/stat CPU calculator
104
+ │ │ ├── ramStats.py # /proc/meminfo RAM calculator
105
+ │ │ ├── networkStats.py # /proc/net/dev throughput calculator
106
+ │ │ ├── DiskStats.py # /proc/diskstats I/O & storage parser
107
+ │ │ └── programmList.py # /proc process parser & cache thread
108
+ │ ├── storage/
109
+ │ │ └── db.py # SQLite metrics storage & historical queries
110
+ │ └── ui/
111
+ │ └── tui/
112
+ │ ├── tui.py # Main Textual App
113
+ │ ├── css/
114
+ │ │ └── tui.css # TUI styling & themes
115
+ │ ├── components/
116
+ │ │ ├── menu.py # Settings sidebar component
117
+ │ │ └── widgetManager.py # Dynamic grid layout organizer
118
+ │ └── widgets/
119
+ │ ├── brailleGraph.py # High-density Unicode graph
120
+ │ ├── cpuWidget.py # CPU monitor widget
121
+ │ ├── ramWidget.py # RAM monitor widget
122
+ │ ├── networkWidget.py # Network monitor widget
123
+ │ ├── diskWidget.py # Disk I/O & storage widget
124
+ │ ├── programmListWidget.py # Interactive process list
125
+ │ ├── procInfoWidget.py # Process details panel
126
+ │ └── statusWidget.py # Host & kernel status
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Requirements
132
+
133
+ * **Python:** 3.10+
134
+ * **OS:** Linux (relies on `/proc` and `/sys` filesystems)
135
+ * **Terminal:** Minimum 80 columns × 24 lines recommended
@@ -0,0 +1,123 @@
1
+ # Monitorify
2
+
3
+ A lightweight system monitor that runs right in your terminal. It tracks CPU usage, memory consumption, network activity, disk metrics, and process states in real time with historical graphing — no bloated dependencies, just a clean, high-performance TUI dashboard.
4
+
5
+ Built with Python and [Textual](https://github.com/Textualize/textual). Reads directly from `/proc` and `/sys` for lightning-fast, native performance on Linux.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ * **CPU & System Info** — Live CPU percentage, multi-timeframe braille graphing, OS kernel, hostname, and system uptime.
12
+ * **Memory (RAM)** — Real-time memory utilization, percentage, and historical graph.
13
+ * **Network Throughput** — Split-view upload/download graph with current speed, peak throughput, and total transferred bytes.
14
+ * **Disk Metrics** — I/O activity rates (reads/writes) and storage capacity breakdown for physical disks.
15
+ * **Process Monitor** — Interactive list of running processes with CPU/RAM metrics, process state, uptime, and real-time inspector.
16
+ * **Background Collector Daemon** — Lightweight collector logging metrics to an SQLite database (`~/.local/share/monitorify/monitorify.db`) with automatic pruning.
17
+ * **Customizable Menu (`m`)** — Toggle widget visibility, adjust collection frequency, and set history graph duration (1m, 5m, 10m, 30m, 1h, or custom durations like `2h`, `1d`).
18
+
19
+ ---
20
+
21
+ ## Quick Start
22
+
23
+ ```bash
24
+ # Recommended — isolated install via pipx
25
+ pipx install git+https://github.com/leonst036/Monitorify.git
26
+
27
+ # Then launch the dashboard
28
+ monitorify
29
+ ```
30
+
31
+ > For full installation options (pip, source, systemd daemon) see **[INSTALL.md](INSTALL.md)**.
32
+
33
+ ---
34
+
35
+ ## Usage
36
+
37
+ ### Dashboard
38
+
39
+ ```bash
40
+ monitorify
41
+ ```
42
+
43
+ | Key | Action |
44
+ |-----|--------|
45
+ | `m` | Open settings menu (toggle widgets, set intervals, change history window) |
46
+ | `q` / `c` | Quit |
47
+
48
+ ### Standalone Background Daemon
49
+
50
+ ```bash
51
+ monitorify-daemon
52
+ ```
53
+
54
+ Metrics are written to `~/.local/share/monitorify/monitorify.db` continuously.
55
+
56
+ ---
57
+
58
+ ## Configuration & Paths
59
+
60
+ Monitorify follows XDG base directory conventions:
61
+
62
+ | Purpose | Default Path | Environment Variable |
63
+ |---|---|---|
64
+ | **Database** | `~/.local/share/monitorify/monitorify.db` | `MONITORIFY_DB_PATH` |
65
+ | **Daemon PID** | `/run/user/<uid>/monitorify/daemon.pid` | `MONITORIFY_PID_PATH` |
66
+ | **Interval** | `1.0` seconds | `MONITORIFY_INTERVAL` |
67
+ | **Retention** | `604800` seconds (7 days) | `MONITORIFY_RETENTION` |
68
+
69
+ ---
70
+
71
+ ## Project Structure
72
+
73
+ ```
74
+ Monitorify/
75
+ ├── pyproject.toml # Package build configuration & entry points
76
+ ├── README.md # This file
77
+ ├── INSTALL.md # Full installation guide
78
+ ├── main.py # Developer entrypoint shim
79
+ ├── src/
80
+ │ └── monitorify/
81
+ │ ├── __init__.py # Package metadata (__version__)
82
+ │ ├── __main__.py # python -m monitorify entrypoint
83
+ │ ├── config.py # XDG path handling & environment settings
84
+ │ ├── main.py # Dashboard runner & daemon supervisor
85
+ │ ├── daemon.py # Standalone metrics collector daemon
86
+ │ ├── collector/
87
+ │ │ ├── manager.py # Worker lifecycle coordinator
88
+ │ │ ├── worker.py # Background metric collection thread
89
+ │ │ └── schema.py # Dataclass metric structures
90
+ │ ├── stats/
91
+ │ │ ├── cpuStats.py # /proc/stat CPU calculator
92
+ │ │ ├── ramStats.py # /proc/meminfo RAM calculator
93
+ │ │ ├── networkStats.py # /proc/net/dev throughput calculator
94
+ │ │ ├── DiskStats.py # /proc/diskstats I/O & storage parser
95
+ │ │ └── programmList.py # /proc process parser & cache thread
96
+ │ ├── storage/
97
+ │ │ └── db.py # SQLite metrics storage & historical queries
98
+ │ └── ui/
99
+ │ └── tui/
100
+ │ ├── tui.py # Main Textual App
101
+ │ ├── css/
102
+ │ │ └── tui.css # TUI styling & themes
103
+ │ ├── components/
104
+ │ │ ├── menu.py # Settings sidebar component
105
+ │ │ └── widgetManager.py # Dynamic grid layout organizer
106
+ │ └── widgets/
107
+ │ ├── brailleGraph.py # High-density Unicode graph
108
+ │ ├── cpuWidget.py # CPU monitor widget
109
+ │ ├── ramWidget.py # RAM monitor widget
110
+ │ ├── networkWidget.py # Network monitor widget
111
+ │ ├── diskWidget.py # Disk I/O & storage widget
112
+ │ ├── programmListWidget.py # Interactive process list
113
+ │ ├── procInfoWidget.py # Process details panel
114
+ │ └── statusWidget.py # Host & kernel status
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Requirements
120
+
121
+ * **Python:** 3.10+
122
+ * **OS:** Linux (relies on `/proc` and `/sys` filesystems)
123
+ * **Terminal:** Minimum 80 columns × 24 lines recommended
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "monitorify"
7
+ version = "1.0.0"
8
+ description = "A lightweight Linux terminal system monitor built with Textual"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Leon" }
12
+ ]
13
+ requires-python = ">=3.10"
14
+ license = { text = "MIT" }
15
+ dependencies = [
16
+ "textual>=0.80.0",
17
+ "platformdirs>=4.0.0",
18
+ "rich>=13.0.0",
19
+ ]
20
+
21
+ [project.scripts]
22
+ monitorify = "monitorify.main:cli"
23
+ monitorify-daemon = "monitorify.daemon:main"
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["src"]
27
+
28
+ [tool.setuptools.package-data]
29
+ "monitorify.ui.tui" = ["css/*.css"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Monitorify - A lightweight Linux terminal system monitor."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from monitorify.main import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
@@ -0,0 +1,5 @@
1
+ from .manager import Manager
2
+ from .worker import Worker
3
+ from .schema import MetricSnapshot, NetworkMetrics
4
+
5
+ __all__ = ["Manager", "Worker", "MetricSnapshot", "NetworkMetrics"]
@@ -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
+ )
@@ -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)))
@@ -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()
@@ -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