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.
- monitorify-1.0.0/PKG-INFO +135 -0
- monitorify-1.0.0/README.md +123 -0
- monitorify-1.0.0/pyproject.toml +29 -0
- monitorify-1.0.0/setup.cfg +4 -0
- monitorify-1.0.0/src/monitorify/__init__.py +3 -0
- monitorify-1.0.0/src/monitorify/__main__.py +4 -0
- monitorify-1.0.0/src/monitorify/collector/__init__.py +5 -0
- monitorify-1.0.0/src/monitorify/collector/manager.py +48 -0
- monitorify-1.0.0/src/monitorify/collector/schema.py +22 -0
- monitorify-1.0.0/src/monitorify/collector/worker.py +44 -0
- monitorify-1.0.0/src/monitorify/config.py +26 -0
- monitorify-1.0.0/src/monitorify/daemon.py +32 -0
- monitorify-1.0.0/src/monitorify/main.py +53 -0
- monitorify-1.0.0/src/monitorify/stats/DiskStats.py +108 -0
- monitorify-1.0.0/src/monitorify/stats/__init__.py +1 -0
- monitorify-1.0.0/src/monitorify/stats/cpuStats.py +46 -0
- monitorify-1.0.0/src/monitorify/stats/networkStats.py +69 -0
- monitorify-1.0.0/src/monitorify/stats/programmList.py +272 -0
- monitorify-1.0.0/src/monitorify/stats/ramStats.py +18 -0
- monitorify-1.0.0/src/monitorify/storage/__init__.py +3 -0
- monitorify-1.0.0/src/monitorify/storage/db.py +154 -0
- monitorify-1.0.0/src/monitorify/ui/__init__.py +1 -0
- monitorify-1.0.0/src/monitorify/ui/tui/__init__.py +1 -0
- monitorify-1.0.0/src/monitorify/ui/tui/components/menu.py +180 -0
- monitorify-1.0.0/src/monitorify/ui/tui/components/widgetManager.py +43 -0
- monitorify-1.0.0/src/monitorify/ui/tui/css/tui.css +168 -0
- monitorify-1.0.0/src/monitorify/ui/tui/tui.py +187 -0
- monitorify-1.0.0/src/monitorify/ui/tui/widgets/__init__.py +1 -0
- monitorify-1.0.0/src/monitorify/ui/tui/widgets/brailleGraph.py +261 -0
- monitorify-1.0.0/src/monitorify/ui/tui/widgets/cpuWidget.py +46 -0
- monitorify-1.0.0/src/monitorify/ui/tui/widgets/diskWidget.py +207 -0
- monitorify-1.0.0/src/monitorify/ui/tui/widgets/networkWidget.py +98 -0
- monitorify-1.0.0/src/monitorify/ui/tui/widgets/procInfoWidget.py +44 -0
- monitorify-1.0.0/src/monitorify/ui/tui/widgets/programmListWidget.py +202 -0
- monitorify-1.0.0/src/monitorify/ui/tui/widgets/ramWidget.py +36 -0
- monitorify-1.0.0/src/monitorify/ui/tui/widgets/statusWidget.py +45 -0
- monitorify-1.0.0/src/monitorify.egg-info/PKG-INFO +135 -0
- monitorify-1.0.0/src/monitorify.egg-info/SOURCES.txt +40 -0
- monitorify-1.0.0/src/monitorify.egg-info/dependency_links.txt +1 -0
- monitorify-1.0.0/src/monitorify.egg-info/entry_points.txt +3 -0
- monitorify-1.0.0/src/monitorify.egg-info/requires.txt +3 -0
- 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,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
|