cpu-loader 0.0.2__tar.gz → 0.1.2__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.
- {cpu_loader-0.0.2/src/cpu_loader.egg-info → cpu_loader-0.1.2}/PKG-INFO +146 -21
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/README.md +144 -20
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/pyproject.toml +3 -2
- {cpu_loader-0.0.2 → cpu_loader-0.1.2/src/cpu_loader.egg-info}/PKG-INFO +146 -21
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/src/cpu_loader.egg-info/SOURCES.txt +1 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/src/cpu_loader.egg-info/requires.txt +1 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/src/cpu_loader.egg-info/top_level.txt +1 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/src/main.py +125 -6
- cpu_loader-0.1.2/src/mqtt_publisher.py +189 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/LICENSE +0 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/MANIFEST.in +0 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/setup.cfg +0 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/setup.py +0 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/src/cpu_loader.egg-info/dependency_links.txt +0 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/src/cpu_loader.egg-info/entry_points.txt +0 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/src/cpu_loader.py +0 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/src/cpu_loader_core.c +0 -0
- {cpu_loader-0.0.2 → cpu_loader-0.1.2}/src/example.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cpu-loader
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API
|
|
5
5
|
Author-email: the78mole <the78mole@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
@@ -33,6 +33,7 @@ Requires-Dist: pydantic==2.10.0
|
|
|
33
33
|
Requires-Dist: requests==2.32.3
|
|
34
34
|
Requires-Dist: psutil>=5.9.0
|
|
35
35
|
Requires-Dist: websockets>=12.0
|
|
36
|
+
Requires-Dist: paho-mqtt>=2.1.0
|
|
36
37
|
Dynamic: license-file
|
|
37
38
|
|
|
38
39
|
# CPU Loader
|
|
@@ -44,26 +45,48 @@ Dynamic: license-file
|
|
|
44
45
|
|
|
45
46
|
A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API.
|
|
46
47
|
|
|
47
|
-
## Features
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
48
|
+
## ✨ Key Features
|
|
49
|
+
|
|
50
|
+
- **🎯 Precise Per-Thread Control**: Set individual CPU load (0-100%) for each core independently
|
|
51
|
+
- **⚡ High Performance**: Native C implementation with pthreads ensures accurate load generation
|
|
52
|
+
- **📊 Real-Time Monitoring**: Live WebSocket updates showing actual CPU usage via `psutil`
|
|
53
|
+
- **🎛️ Interactive WebUI**: Beautiful gradient interface with sliders and visual feedback
|
|
54
|
+
- **🚀 REST API**: Complete programmatic control for automation and testing
|
|
55
|
+
- **📱 Responsive Design**: Works seamlessly on desktop and mobile devices
|
|
56
|
+
- **🔄 Instant Updates**: Changes take effect immediately with sub-second response time
|
|
57
|
+
- 📡 **MQTT Integration**: Publish metrics and settings to MQTT broker for IoT/monitoring systems
|
|
58
|
+
|
|
59
|
+
## 🖼️ Screenshots
|
|
60
|
+
|
|
61
|
+
### Desktop Interface
|
|
62
|
+
<table>
|
|
63
|
+
<tr>
|
|
64
|
+
<td width="50%">
|
|
65
|
+
<img src="screenshots/01-initial-view.png" alt="Initial view with zero load">
|
|
66
|
+
<p align="center"><em>Clean interface showing all CPU threads at idle</em></p>
|
|
67
|
+
</td>
|
|
68
|
+
<td width="50%">
|
|
69
|
+
<img src="screenshots/02-with-load.png" alt="Mixed load across threads">
|
|
70
|
+
<p align="center"><em>Different load levels: 50%, 75%, and 25% on individual threads</em></p>
|
|
71
|
+
</td>
|
|
72
|
+
</tr>
|
|
73
|
+
<tr>
|
|
74
|
+
<td width="50%">
|
|
75
|
+
<img src="screenshots/03-live-metrics.png" alt="Live CPU metrics updating">
|
|
76
|
+
<p align="center"><em>Real-time metrics showing actual CPU usage with color-coded bars</em></p>
|
|
77
|
+
</td>
|
|
78
|
+
<td width="50%">
|
|
79
|
+
<img src="screenshots/04-mobile-view.png" alt="Mobile responsive view">
|
|
80
|
+
<p align="center"><em>Fully responsive mobile interface</em></p>
|
|
81
|
+
</td>
|
|
82
|
+
</tr>
|
|
83
|
+
</table>
|
|
84
|
+
|
|
85
|
+
### Features Shown
|
|
86
|
+
- **Visual Load Bars**: Green progress bars show target load, blue bars display actual CPU usage
|
|
87
|
+
- **Real-Time Updates**: WebSocket connection provides live metrics every second
|
|
88
|
+
- **Preset Buttons**: Quick-set options (0%, 25%, 50%, 75%, 100%) for all threads
|
|
89
|
+
- **Smooth Gradients**: Modern UI with purple-to-blue gradient design
|
|
67
90
|
|
|
68
91
|
## Installation
|
|
69
92
|
|
|
@@ -112,6 +135,22 @@ uv run src/main.py
|
|
|
112
135
|
|
|
113
136
|
The server will start on `http://localhost:8000`
|
|
114
137
|
|
|
138
|
+
#### Command-Line Options
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
uv run src/main.py --help
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Available Options:**
|
|
145
|
+
- `--host HOST`: Host to bind the server to (default: 0.0.0.0)
|
|
146
|
+
- `--port PORT`: Port to bind the server to (default: 8000)
|
|
147
|
+
- `--mqtt-broker-host HOST`: MQTT broker hostname
|
|
148
|
+
- `--mqtt-broker-port PORT`: MQTT broker port (default: 1883)
|
|
149
|
+
- `--mqtt-username USER`: MQTT username
|
|
150
|
+
- `--mqtt-password PASS`: MQTT password
|
|
151
|
+
- `--mqtt-topic-prefix PREFIX`: MQTT topic prefix (default: cpu-loader)
|
|
152
|
+
- `--mqtt-client-id ID`: MQTT client ID (default: cpu-loader)
|
|
153
|
+
|
|
115
154
|
### WebUI
|
|
116
155
|
|
|
117
156
|
Open your browser and navigate to `http://localhost:8000`
|
|
@@ -167,6 +206,88 @@ curl -X POST http://localhost:8000/api/threads \
|
|
|
167
206
|
|
|
168
207
|
Once the server is running, visit `http://localhost:8000/docs` for interactive API documentation powered by Swagger UI.
|
|
169
208
|
|
|
209
|
+
## MQTT Publishing
|
|
210
|
+
|
|
211
|
+
CPU Loader can publish real-time CPU metrics and load control settings to an MQTT broker for integration with home automation systems, monitoring tools, or custom applications.
|
|
212
|
+
|
|
213
|
+
### MQTT Topics
|
|
214
|
+
|
|
215
|
+
The application publishes to two topics:
|
|
216
|
+
|
|
217
|
+
1. **`{prefix}/cpu_metrics`**: Published every second with current CPU utilization
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"total_cpu_percent": 25.5,
|
|
221
|
+
"per_cpu_percent": [25.0, 26.0, 25.3, 25.7]
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
2. **`{prefix}/load_settings`**: Published when load settings change (retained message)
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"num_threads": 4,
|
|
229
|
+
"loads": {"0": 25.0, "1": 25.0, "2": 25.0, "3": 25.0},
|
|
230
|
+
"average_load": 25.0
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Configuration
|
|
235
|
+
|
|
236
|
+
MQTT can be configured using environment variables or command-line arguments. Command-line arguments take precedence over environment variables.
|
|
237
|
+
|
|
238
|
+
#### Using Environment Variables
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
export MQTT_BROKER_HOST=mqtt.example.com
|
|
242
|
+
export MQTT_BROKER_PORT=1883
|
|
243
|
+
export MQTT_USERNAME=myuser
|
|
244
|
+
export MQTT_PASSWORD=mypassword
|
|
245
|
+
export MQTT_TOPIC_PREFIX=cpu-loader
|
|
246
|
+
export MQTT_CLIENT_ID=cpu-loader-001
|
|
247
|
+
|
|
248
|
+
uv run src/main.py
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Using Command-Line Arguments
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
uv run src/main.py \
|
|
255
|
+
--mqtt-broker-host mqtt.example.com \
|
|
256
|
+
--mqtt-broker-port 1883 \
|
|
257
|
+
--mqtt-username myuser \
|
|
258
|
+
--mqtt-password mypassword \
|
|
259
|
+
--mqtt-topic-prefix cpu-loader \
|
|
260
|
+
--mqtt-client-id cpu-loader-001
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### Testing with Mosquitto
|
|
264
|
+
|
|
265
|
+
To test MQTT publishing locally:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# Install mosquitto
|
|
269
|
+
sudo apt-get install mosquitto mosquitto-clients
|
|
270
|
+
|
|
271
|
+
# Start CPU Loader with MQTT
|
|
272
|
+
uv run src/main.py --mqtt-broker-host localhost
|
|
273
|
+
|
|
274
|
+
# Subscribe to all topics (in another terminal)
|
|
275
|
+
mosquitto_sub -h localhost -t "cpu-loader/#" -v
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### MQTT Settings
|
|
279
|
+
|
|
280
|
+
| Setting | Environment Variable | CLI Argument | Default | Description |
|
|
281
|
+
|---------|---------------------|--------------|---------|-------------|
|
|
282
|
+
| Broker Host | `MQTT_BROKER_HOST` | `--mqtt-broker-host` | None | MQTT broker hostname or IP |
|
|
283
|
+
| Broker Port | `MQTT_BROKER_PORT` | `--mqtt-broker-port` | 1883 | MQTT broker port |
|
|
284
|
+
| Username | `MQTT_USERNAME` | `--mqtt-username` | None | MQTT authentication username |
|
|
285
|
+
| Password | `MQTT_PASSWORD` | `--mqtt-password` | None | MQTT authentication password |
|
|
286
|
+
| Topic Prefix | `MQTT_TOPIC_PREFIX` | `--mqtt-topic-prefix` | cpu-loader | Prefix for all MQTT topics |
|
|
287
|
+
| Client ID | `MQTT_CLIENT_ID` | `--mqtt-client-id` | cpu-loader | MQTT client identifier |
|
|
288
|
+
|
|
289
|
+
**Note:** If no MQTT broker host is configured, MQTT publishing will be disabled and the application will function normally without it.
|
|
290
|
+
|
|
170
291
|
## Example Script
|
|
171
292
|
|
|
172
293
|
An example script (`src/example.py`) is provided to demonstrate programmatic control:
|
|
@@ -191,6 +312,7 @@ The example demonstrates:
|
|
|
191
312
|
- **src/cpu_loader_core.c**: High-performance C implementation using pthreads for CPU load generation
|
|
192
313
|
- **src/cpu_loader.py**: Python wrapper providing a clean API to the C extension
|
|
193
314
|
- **src/main.py**: FastAPI application with REST API and embedded WebUI
|
|
315
|
+
- **src/mqtt_publisher.py**: MQTT client for publishing metrics and settings
|
|
194
316
|
- **Threading Model**: Native pthreads for maximum efficiency and precise timing
|
|
195
317
|
- **Load Algorithm**: High-resolution busy-wait loops with nanosecond precision
|
|
196
318
|
|
|
@@ -200,6 +322,7 @@ The example demonstrates:
|
|
|
200
322
|
- FastAPI 0.115.0+
|
|
201
323
|
- Uvicorn 0.32.0+
|
|
202
324
|
- Pydantic 2.10.0+
|
|
325
|
+
- paho-mqtt 2.1.0+ (for MQTT publishing)
|
|
203
326
|
- C compiler (gcc or clang) for building the extension
|
|
204
327
|
|
|
205
328
|
## Use Cases
|
|
@@ -209,6 +332,8 @@ The example demonstrates:
|
|
|
209
332
|
- **Thermal Testing**: Check cooling system effectiveness
|
|
210
333
|
- **Power Consumption Analysis**: Measure power usage at different load levels
|
|
211
334
|
- **Benchmarking**: Create reproducible load scenarios
|
|
335
|
+
- **IoT Integration**: Integrate with Home Assistant, Node-RED, or other MQTT-based systems
|
|
336
|
+
- **Monitoring**: Feed CPU metrics into monitoring dashboards via MQTT
|
|
212
337
|
|
|
213
338
|
## Development
|
|
214
339
|
|
|
@@ -7,26 +7,48 @@
|
|
|
7
7
|
|
|
8
8
|
A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API.
|
|
9
9
|
|
|
10
|
-
## Features
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
## ✨ Key Features
|
|
11
|
+
|
|
12
|
+
- **🎯 Precise Per-Thread Control**: Set individual CPU load (0-100%) for each core independently
|
|
13
|
+
- **⚡ High Performance**: Native C implementation with pthreads ensures accurate load generation
|
|
14
|
+
- **📊 Real-Time Monitoring**: Live WebSocket updates showing actual CPU usage via `psutil`
|
|
15
|
+
- **🎛️ Interactive WebUI**: Beautiful gradient interface with sliders and visual feedback
|
|
16
|
+
- **🚀 REST API**: Complete programmatic control for automation and testing
|
|
17
|
+
- **📱 Responsive Design**: Works seamlessly on desktop and mobile devices
|
|
18
|
+
- **🔄 Instant Updates**: Changes take effect immediately with sub-second response time
|
|
19
|
+
- 📡 **MQTT Integration**: Publish metrics and settings to MQTT broker for IoT/monitoring systems
|
|
20
|
+
|
|
21
|
+
## 🖼️ Screenshots
|
|
22
|
+
|
|
23
|
+
### Desktop Interface
|
|
24
|
+
<table>
|
|
25
|
+
<tr>
|
|
26
|
+
<td width="50%">
|
|
27
|
+
<img src="screenshots/01-initial-view.png" alt="Initial view with zero load">
|
|
28
|
+
<p align="center"><em>Clean interface showing all CPU threads at idle</em></p>
|
|
29
|
+
</td>
|
|
30
|
+
<td width="50%">
|
|
31
|
+
<img src="screenshots/02-with-load.png" alt="Mixed load across threads">
|
|
32
|
+
<p align="center"><em>Different load levels: 50%, 75%, and 25% on individual threads</em></p>
|
|
33
|
+
</td>
|
|
34
|
+
</tr>
|
|
35
|
+
<tr>
|
|
36
|
+
<td width="50%">
|
|
37
|
+
<img src="screenshots/03-live-metrics.png" alt="Live CPU metrics updating">
|
|
38
|
+
<p align="center"><em>Real-time metrics showing actual CPU usage with color-coded bars</em></p>
|
|
39
|
+
</td>
|
|
40
|
+
<td width="50%">
|
|
41
|
+
<img src="screenshots/04-mobile-view.png" alt="Mobile responsive view">
|
|
42
|
+
<p align="center"><em>Fully responsive mobile interface</em></p>
|
|
43
|
+
</td>
|
|
44
|
+
</tr>
|
|
45
|
+
</table>
|
|
46
|
+
|
|
47
|
+
### Features Shown
|
|
48
|
+
- **Visual Load Bars**: Green progress bars show target load, blue bars display actual CPU usage
|
|
49
|
+
- **Real-Time Updates**: WebSocket connection provides live metrics every second
|
|
50
|
+
- **Preset Buttons**: Quick-set options (0%, 25%, 50%, 75%, 100%) for all threads
|
|
51
|
+
- **Smooth Gradients**: Modern UI with purple-to-blue gradient design
|
|
30
52
|
|
|
31
53
|
## Installation
|
|
32
54
|
|
|
@@ -75,6 +97,22 @@ uv run src/main.py
|
|
|
75
97
|
|
|
76
98
|
The server will start on `http://localhost:8000`
|
|
77
99
|
|
|
100
|
+
#### Command-Line Options
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
uv run src/main.py --help
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Available Options:**
|
|
107
|
+
- `--host HOST`: Host to bind the server to (default: 0.0.0.0)
|
|
108
|
+
- `--port PORT`: Port to bind the server to (default: 8000)
|
|
109
|
+
- `--mqtt-broker-host HOST`: MQTT broker hostname
|
|
110
|
+
- `--mqtt-broker-port PORT`: MQTT broker port (default: 1883)
|
|
111
|
+
- `--mqtt-username USER`: MQTT username
|
|
112
|
+
- `--mqtt-password PASS`: MQTT password
|
|
113
|
+
- `--mqtt-topic-prefix PREFIX`: MQTT topic prefix (default: cpu-loader)
|
|
114
|
+
- `--mqtt-client-id ID`: MQTT client ID (default: cpu-loader)
|
|
115
|
+
|
|
78
116
|
### WebUI
|
|
79
117
|
|
|
80
118
|
Open your browser and navigate to `http://localhost:8000`
|
|
@@ -130,6 +168,88 @@ curl -X POST http://localhost:8000/api/threads \
|
|
|
130
168
|
|
|
131
169
|
Once the server is running, visit `http://localhost:8000/docs` for interactive API documentation powered by Swagger UI.
|
|
132
170
|
|
|
171
|
+
## MQTT Publishing
|
|
172
|
+
|
|
173
|
+
CPU Loader can publish real-time CPU metrics and load control settings to an MQTT broker for integration with home automation systems, monitoring tools, or custom applications.
|
|
174
|
+
|
|
175
|
+
### MQTT Topics
|
|
176
|
+
|
|
177
|
+
The application publishes to two topics:
|
|
178
|
+
|
|
179
|
+
1. **`{prefix}/cpu_metrics`**: Published every second with current CPU utilization
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"total_cpu_percent": 25.5,
|
|
183
|
+
"per_cpu_percent": [25.0, 26.0, 25.3, 25.7]
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
2. **`{prefix}/load_settings`**: Published when load settings change (retained message)
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"num_threads": 4,
|
|
191
|
+
"loads": {"0": 25.0, "1": 25.0, "2": 25.0, "3": 25.0},
|
|
192
|
+
"average_load": 25.0
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Configuration
|
|
197
|
+
|
|
198
|
+
MQTT can be configured using environment variables or command-line arguments. Command-line arguments take precedence over environment variables.
|
|
199
|
+
|
|
200
|
+
#### Using Environment Variables
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
export MQTT_BROKER_HOST=mqtt.example.com
|
|
204
|
+
export MQTT_BROKER_PORT=1883
|
|
205
|
+
export MQTT_USERNAME=myuser
|
|
206
|
+
export MQTT_PASSWORD=mypassword
|
|
207
|
+
export MQTT_TOPIC_PREFIX=cpu-loader
|
|
208
|
+
export MQTT_CLIENT_ID=cpu-loader-001
|
|
209
|
+
|
|
210
|
+
uv run src/main.py
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### Using Command-Line Arguments
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
uv run src/main.py \
|
|
217
|
+
--mqtt-broker-host mqtt.example.com \
|
|
218
|
+
--mqtt-broker-port 1883 \
|
|
219
|
+
--mqtt-username myuser \
|
|
220
|
+
--mqtt-password mypassword \
|
|
221
|
+
--mqtt-topic-prefix cpu-loader \
|
|
222
|
+
--mqtt-client-id cpu-loader-001
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### Testing with Mosquitto
|
|
226
|
+
|
|
227
|
+
To test MQTT publishing locally:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Install mosquitto
|
|
231
|
+
sudo apt-get install mosquitto mosquitto-clients
|
|
232
|
+
|
|
233
|
+
# Start CPU Loader with MQTT
|
|
234
|
+
uv run src/main.py --mqtt-broker-host localhost
|
|
235
|
+
|
|
236
|
+
# Subscribe to all topics (in another terminal)
|
|
237
|
+
mosquitto_sub -h localhost -t "cpu-loader/#" -v
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### MQTT Settings
|
|
241
|
+
|
|
242
|
+
| Setting | Environment Variable | CLI Argument | Default | Description |
|
|
243
|
+
|---------|---------------------|--------------|---------|-------------|
|
|
244
|
+
| Broker Host | `MQTT_BROKER_HOST` | `--mqtt-broker-host` | None | MQTT broker hostname or IP |
|
|
245
|
+
| Broker Port | `MQTT_BROKER_PORT` | `--mqtt-broker-port` | 1883 | MQTT broker port |
|
|
246
|
+
| Username | `MQTT_USERNAME` | `--mqtt-username` | None | MQTT authentication username |
|
|
247
|
+
| Password | `MQTT_PASSWORD` | `--mqtt-password` | None | MQTT authentication password |
|
|
248
|
+
| Topic Prefix | `MQTT_TOPIC_PREFIX` | `--mqtt-topic-prefix` | cpu-loader | Prefix for all MQTT topics |
|
|
249
|
+
| Client ID | `MQTT_CLIENT_ID` | `--mqtt-client-id` | cpu-loader | MQTT client identifier |
|
|
250
|
+
|
|
251
|
+
**Note:** If no MQTT broker host is configured, MQTT publishing will be disabled and the application will function normally without it.
|
|
252
|
+
|
|
133
253
|
## Example Script
|
|
134
254
|
|
|
135
255
|
An example script (`src/example.py`) is provided to demonstrate programmatic control:
|
|
@@ -154,6 +274,7 @@ The example demonstrates:
|
|
|
154
274
|
- **src/cpu_loader_core.c**: High-performance C implementation using pthreads for CPU load generation
|
|
155
275
|
- **src/cpu_loader.py**: Python wrapper providing a clean API to the C extension
|
|
156
276
|
- **src/main.py**: FastAPI application with REST API and embedded WebUI
|
|
277
|
+
- **src/mqtt_publisher.py**: MQTT client for publishing metrics and settings
|
|
157
278
|
- **Threading Model**: Native pthreads for maximum efficiency and precise timing
|
|
158
279
|
- **Load Algorithm**: High-resolution busy-wait loops with nanosecond precision
|
|
159
280
|
|
|
@@ -163,6 +284,7 @@ The example demonstrates:
|
|
|
163
284
|
- FastAPI 0.115.0+
|
|
164
285
|
- Uvicorn 0.32.0+
|
|
165
286
|
- Pydantic 2.10.0+
|
|
287
|
+
- paho-mqtt 2.1.0+ (for MQTT publishing)
|
|
166
288
|
- C compiler (gcc or clang) for building the extension
|
|
167
289
|
|
|
168
290
|
## Use Cases
|
|
@@ -172,6 +294,8 @@ The example demonstrates:
|
|
|
172
294
|
- **Thermal Testing**: Check cooling system effectiveness
|
|
173
295
|
- **Power Consumption Analysis**: Measure power usage at different load levels
|
|
174
296
|
- **Benchmarking**: Create reproducible load scenarios
|
|
297
|
+
- **IoT Integration**: Integrate with Home Assistant, Node-RED, or other MQTT-based systems
|
|
298
|
+
- **Monitoring**: Feed CPU metrics into monitoring dashboards via MQTT
|
|
175
299
|
|
|
176
300
|
## Development
|
|
177
301
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cpu-loader"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.8"
|
|
@@ -33,6 +33,7 @@ dependencies = [
|
|
|
33
33
|
"requests==2.32.3",
|
|
34
34
|
"psutil>=5.9.0",
|
|
35
35
|
"websockets>=12.0",
|
|
36
|
+
"paho-mqtt>=2.1.0",
|
|
36
37
|
]
|
|
37
38
|
|
|
38
39
|
[project.scripts]
|
|
@@ -50,4 +51,4 @@ build-backend = "setuptools.build_meta"
|
|
|
50
51
|
|
|
51
52
|
[tool.setuptools]
|
|
52
53
|
package-dir = {"" = "src"}
|
|
53
|
-
py-modules = ["cpu_loader", "main", "example"]
|
|
54
|
+
py-modules = ["cpu_loader", "main", "example", "mqtt_publisher"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cpu-loader
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API
|
|
5
5
|
Author-email: the78mole <the78mole@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
@@ -33,6 +33,7 @@ Requires-Dist: pydantic==2.10.0
|
|
|
33
33
|
Requires-Dist: requests==2.32.3
|
|
34
34
|
Requires-Dist: psutil>=5.9.0
|
|
35
35
|
Requires-Dist: websockets>=12.0
|
|
36
|
+
Requires-Dist: paho-mqtt>=2.1.0
|
|
36
37
|
Dynamic: license-file
|
|
37
38
|
|
|
38
39
|
# CPU Loader
|
|
@@ -44,26 +45,48 @@ Dynamic: license-file
|
|
|
44
45
|
|
|
45
46
|
A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API.
|
|
46
47
|
|
|
47
|
-
## Features
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
48
|
+
## ✨ Key Features
|
|
49
|
+
|
|
50
|
+
- **🎯 Precise Per-Thread Control**: Set individual CPU load (0-100%) for each core independently
|
|
51
|
+
- **⚡ High Performance**: Native C implementation with pthreads ensures accurate load generation
|
|
52
|
+
- **📊 Real-Time Monitoring**: Live WebSocket updates showing actual CPU usage via `psutil`
|
|
53
|
+
- **🎛️ Interactive WebUI**: Beautiful gradient interface with sliders and visual feedback
|
|
54
|
+
- **🚀 REST API**: Complete programmatic control for automation and testing
|
|
55
|
+
- **📱 Responsive Design**: Works seamlessly on desktop and mobile devices
|
|
56
|
+
- **🔄 Instant Updates**: Changes take effect immediately with sub-second response time
|
|
57
|
+
- 📡 **MQTT Integration**: Publish metrics and settings to MQTT broker for IoT/monitoring systems
|
|
58
|
+
|
|
59
|
+
## 🖼️ Screenshots
|
|
60
|
+
|
|
61
|
+
### Desktop Interface
|
|
62
|
+
<table>
|
|
63
|
+
<tr>
|
|
64
|
+
<td width="50%">
|
|
65
|
+
<img src="screenshots/01-initial-view.png" alt="Initial view with zero load">
|
|
66
|
+
<p align="center"><em>Clean interface showing all CPU threads at idle</em></p>
|
|
67
|
+
</td>
|
|
68
|
+
<td width="50%">
|
|
69
|
+
<img src="screenshots/02-with-load.png" alt="Mixed load across threads">
|
|
70
|
+
<p align="center"><em>Different load levels: 50%, 75%, and 25% on individual threads</em></p>
|
|
71
|
+
</td>
|
|
72
|
+
</tr>
|
|
73
|
+
<tr>
|
|
74
|
+
<td width="50%">
|
|
75
|
+
<img src="screenshots/03-live-metrics.png" alt="Live CPU metrics updating">
|
|
76
|
+
<p align="center"><em>Real-time metrics showing actual CPU usage with color-coded bars</em></p>
|
|
77
|
+
</td>
|
|
78
|
+
<td width="50%">
|
|
79
|
+
<img src="screenshots/04-mobile-view.png" alt="Mobile responsive view">
|
|
80
|
+
<p align="center"><em>Fully responsive mobile interface</em></p>
|
|
81
|
+
</td>
|
|
82
|
+
</tr>
|
|
83
|
+
</table>
|
|
84
|
+
|
|
85
|
+
### Features Shown
|
|
86
|
+
- **Visual Load Bars**: Green progress bars show target load, blue bars display actual CPU usage
|
|
87
|
+
- **Real-Time Updates**: WebSocket connection provides live metrics every second
|
|
88
|
+
- **Preset Buttons**: Quick-set options (0%, 25%, 50%, 75%, 100%) for all threads
|
|
89
|
+
- **Smooth Gradients**: Modern UI with purple-to-blue gradient design
|
|
67
90
|
|
|
68
91
|
## Installation
|
|
69
92
|
|
|
@@ -112,6 +135,22 @@ uv run src/main.py
|
|
|
112
135
|
|
|
113
136
|
The server will start on `http://localhost:8000`
|
|
114
137
|
|
|
138
|
+
#### Command-Line Options
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
uv run src/main.py --help
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Available Options:**
|
|
145
|
+
- `--host HOST`: Host to bind the server to (default: 0.0.0.0)
|
|
146
|
+
- `--port PORT`: Port to bind the server to (default: 8000)
|
|
147
|
+
- `--mqtt-broker-host HOST`: MQTT broker hostname
|
|
148
|
+
- `--mqtt-broker-port PORT`: MQTT broker port (default: 1883)
|
|
149
|
+
- `--mqtt-username USER`: MQTT username
|
|
150
|
+
- `--mqtt-password PASS`: MQTT password
|
|
151
|
+
- `--mqtt-topic-prefix PREFIX`: MQTT topic prefix (default: cpu-loader)
|
|
152
|
+
- `--mqtt-client-id ID`: MQTT client ID (default: cpu-loader)
|
|
153
|
+
|
|
115
154
|
### WebUI
|
|
116
155
|
|
|
117
156
|
Open your browser and navigate to `http://localhost:8000`
|
|
@@ -167,6 +206,88 @@ curl -X POST http://localhost:8000/api/threads \
|
|
|
167
206
|
|
|
168
207
|
Once the server is running, visit `http://localhost:8000/docs` for interactive API documentation powered by Swagger UI.
|
|
169
208
|
|
|
209
|
+
## MQTT Publishing
|
|
210
|
+
|
|
211
|
+
CPU Loader can publish real-time CPU metrics and load control settings to an MQTT broker for integration with home automation systems, monitoring tools, or custom applications.
|
|
212
|
+
|
|
213
|
+
### MQTT Topics
|
|
214
|
+
|
|
215
|
+
The application publishes to two topics:
|
|
216
|
+
|
|
217
|
+
1. **`{prefix}/cpu_metrics`**: Published every second with current CPU utilization
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"total_cpu_percent": 25.5,
|
|
221
|
+
"per_cpu_percent": [25.0, 26.0, 25.3, 25.7]
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
2. **`{prefix}/load_settings`**: Published when load settings change (retained message)
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"num_threads": 4,
|
|
229
|
+
"loads": {"0": 25.0, "1": 25.0, "2": 25.0, "3": 25.0},
|
|
230
|
+
"average_load": 25.0
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Configuration
|
|
235
|
+
|
|
236
|
+
MQTT can be configured using environment variables or command-line arguments. Command-line arguments take precedence over environment variables.
|
|
237
|
+
|
|
238
|
+
#### Using Environment Variables
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
export MQTT_BROKER_HOST=mqtt.example.com
|
|
242
|
+
export MQTT_BROKER_PORT=1883
|
|
243
|
+
export MQTT_USERNAME=myuser
|
|
244
|
+
export MQTT_PASSWORD=mypassword
|
|
245
|
+
export MQTT_TOPIC_PREFIX=cpu-loader
|
|
246
|
+
export MQTT_CLIENT_ID=cpu-loader-001
|
|
247
|
+
|
|
248
|
+
uv run src/main.py
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Using Command-Line Arguments
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
uv run src/main.py \
|
|
255
|
+
--mqtt-broker-host mqtt.example.com \
|
|
256
|
+
--mqtt-broker-port 1883 \
|
|
257
|
+
--mqtt-username myuser \
|
|
258
|
+
--mqtt-password mypassword \
|
|
259
|
+
--mqtt-topic-prefix cpu-loader \
|
|
260
|
+
--mqtt-client-id cpu-loader-001
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### Testing with Mosquitto
|
|
264
|
+
|
|
265
|
+
To test MQTT publishing locally:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# Install mosquitto
|
|
269
|
+
sudo apt-get install mosquitto mosquitto-clients
|
|
270
|
+
|
|
271
|
+
# Start CPU Loader with MQTT
|
|
272
|
+
uv run src/main.py --mqtt-broker-host localhost
|
|
273
|
+
|
|
274
|
+
# Subscribe to all topics (in another terminal)
|
|
275
|
+
mosquitto_sub -h localhost -t "cpu-loader/#" -v
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### MQTT Settings
|
|
279
|
+
|
|
280
|
+
| Setting | Environment Variable | CLI Argument | Default | Description |
|
|
281
|
+
|---------|---------------------|--------------|---------|-------------|
|
|
282
|
+
| Broker Host | `MQTT_BROKER_HOST` | `--mqtt-broker-host` | None | MQTT broker hostname or IP |
|
|
283
|
+
| Broker Port | `MQTT_BROKER_PORT` | `--mqtt-broker-port` | 1883 | MQTT broker port |
|
|
284
|
+
| Username | `MQTT_USERNAME` | `--mqtt-username` | None | MQTT authentication username |
|
|
285
|
+
| Password | `MQTT_PASSWORD` | `--mqtt-password` | None | MQTT authentication password |
|
|
286
|
+
| Topic Prefix | `MQTT_TOPIC_PREFIX` | `--mqtt-topic-prefix` | cpu-loader | Prefix for all MQTT topics |
|
|
287
|
+
| Client ID | `MQTT_CLIENT_ID` | `--mqtt-client-id` | cpu-loader | MQTT client identifier |
|
|
288
|
+
|
|
289
|
+
**Note:** If no MQTT broker host is configured, MQTT publishing will be disabled and the application will function normally without it.
|
|
290
|
+
|
|
170
291
|
## Example Script
|
|
171
292
|
|
|
172
293
|
An example script (`src/example.py`) is provided to demonstrate programmatic control:
|
|
@@ -191,6 +312,7 @@ The example demonstrates:
|
|
|
191
312
|
- **src/cpu_loader_core.c**: High-performance C implementation using pthreads for CPU load generation
|
|
192
313
|
- **src/cpu_loader.py**: Python wrapper providing a clean API to the C extension
|
|
193
314
|
- **src/main.py**: FastAPI application with REST API and embedded WebUI
|
|
315
|
+
- **src/mqtt_publisher.py**: MQTT client for publishing metrics and settings
|
|
194
316
|
- **Threading Model**: Native pthreads for maximum efficiency and precise timing
|
|
195
317
|
- **Load Algorithm**: High-resolution busy-wait loops with nanosecond precision
|
|
196
318
|
|
|
@@ -200,6 +322,7 @@ The example demonstrates:
|
|
|
200
322
|
- FastAPI 0.115.0+
|
|
201
323
|
- Uvicorn 0.32.0+
|
|
202
324
|
- Pydantic 2.10.0+
|
|
325
|
+
- paho-mqtt 2.1.0+ (for MQTT publishing)
|
|
203
326
|
- C compiler (gcc or clang) for building the extension
|
|
204
327
|
|
|
205
328
|
## Use Cases
|
|
@@ -209,6 +332,8 @@ The example demonstrates:
|
|
|
209
332
|
- **Thermal Testing**: Check cooling system effectiveness
|
|
210
333
|
- **Power Consumption Analysis**: Measure power usage at different load levels
|
|
211
334
|
- **Benchmarking**: Create reproducible load scenarios
|
|
335
|
+
- **IoT Integration**: Integrate with Home Assistant, Node-RED, or other MQTT-based systems
|
|
336
|
+
- **Monitoring**: Feed CPU metrics into monitoring dashboards via MQTT
|
|
212
337
|
|
|
213
338
|
## Development
|
|
214
339
|
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
CPU Loader FastAPI Application
|
|
3
3
|
Provides REST API and WebUI for controlling CPU load.
|
|
4
4
|
"""
|
|
5
|
+
import argparse
|
|
5
6
|
import asyncio
|
|
7
|
+
import logging
|
|
6
8
|
from contextlib import asynccontextmanager
|
|
7
|
-
from typing import Dict, List, Set
|
|
9
|
+
from typing import Dict, List, Optional, Set
|
|
8
10
|
|
|
9
11
|
import psutil
|
|
10
12
|
import uvicorn
|
|
@@ -13,6 +15,13 @@ from fastapi.responses import HTMLResponse
|
|
|
13
15
|
from pydantic import BaseModel, Field
|
|
14
16
|
|
|
15
17
|
from cpu_loader import CPULoader
|
|
18
|
+
from mqtt_publisher import MQTTPublisher
|
|
19
|
+
|
|
20
|
+
# Configure logging
|
|
21
|
+
logging.basicConfig(
|
|
22
|
+
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
23
|
+
)
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
16
25
|
|
|
17
26
|
|
|
18
27
|
# Pydantic models for request/response validation
|
|
@@ -44,8 +53,9 @@ class CPUMetricsResponse(BaseModel):
|
|
|
44
53
|
per_cpu_percent: List[float]
|
|
45
54
|
|
|
46
55
|
|
|
47
|
-
# Global CPU loader instance and WebSocket connections
|
|
56
|
+
# Global CPU loader instance, MQTT publisher, and WebSocket connections
|
|
48
57
|
cpu_loader = None
|
|
58
|
+
mqtt_publisher: Optional[MQTTPublisher] = None
|
|
49
59
|
websocket_connections: Set[WebSocket] = set()
|
|
50
60
|
monitoring_task = None
|
|
51
61
|
|
|
@@ -82,17 +92,34 @@ async def cpu_monitoring_loop():
|
|
|
82
92
|
|
|
83
93
|
# Remove disconnected clients
|
|
84
94
|
websocket_connections.difference_update(disconnected)
|
|
95
|
+
|
|
96
|
+
# Publish to MQTT if enabled
|
|
97
|
+
if mqtt_publisher:
|
|
98
|
+
mqtt_publisher.publish_cpu_metrics(total_cpu, per_cpu)
|
|
99
|
+
|
|
85
100
|
except Exception as e:
|
|
86
|
-
|
|
101
|
+
logger.error(f"Error in CPU monitoring loop: {e}")
|
|
87
102
|
await asyncio.sleep(1.0)
|
|
88
103
|
|
|
89
104
|
|
|
90
105
|
@asynccontextmanager
|
|
91
106
|
async def lifespan(app: FastAPI):
|
|
92
107
|
"""Lifespan context manager for startup and shutdown events."""
|
|
93
|
-
global cpu_loader, monitoring_task
|
|
108
|
+
global cpu_loader, mqtt_publisher, monitoring_task
|
|
94
109
|
# Startup
|
|
95
110
|
cpu_loader = CPULoader()
|
|
111
|
+
|
|
112
|
+
# Initialize MQTT publisher with settings from arguments or environment
|
|
113
|
+
mqtt_args = getattr(app.state, "mqtt_args", {})
|
|
114
|
+
try:
|
|
115
|
+
mqtt_publisher = MQTTPublisher(**mqtt_args)
|
|
116
|
+
except ImportError:
|
|
117
|
+
logger.warning("MQTT publishing disabled: paho-mqtt not installed")
|
|
118
|
+
mqtt_publisher = None
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"Failed to initialize MQTT publisher: {e}")
|
|
121
|
+
mqtt_publisher = None
|
|
122
|
+
|
|
96
123
|
monitoring_task = asyncio.create_task(cpu_monitoring_loop())
|
|
97
124
|
yield
|
|
98
125
|
# Shutdown
|
|
@@ -102,6 +129,8 @@ async def lifespan(app: FastAPI):
|
|
|
102
129
|
await monitoring_task
|
|
103
130
|
except asyncio.CancelledError:
|
|
104
131
|
pass
|
|
132
|
+
if mqtt_publisher:
|
|
133
|
+
mqtt_publisher.disconnect()
|
|
105
134
|
cpu_loader.shutdown()
|
|
106
135
|
|
|
107
136
|
|
|
@@ -311,7 +340,7 @@ async def get_webui():
|
|
|
311
340
|
</head>
|
|
312
341
|
<body>
|
|
313
342
|
<div class="container">
|
|
314
|
-
<h1
|
|
343
|
+
<h1>🔥 CPU Loader Control</h1>
|
|
315
344
|
<p class="subtitle">Control CPU load per thread in real-time</p>
|
|
316
345
|
|
|
317
346
|
<div class="status">
|
|
@@ -615,6 +644,13 @@ async def set_thread_count(request: ThreadCountRequest):
|
|
|
615
644
|
"""Set the number of threads."""
|
|
616
645
|
try:
|
|
617
646
|
cpu_loader.set_num_threads(request.num_threads)
|
|
647
|
+
|
|
648
|
+
# Publish updated settings to MQTT
|
|
649
|
+
if mqtt_publisher:
|
|
650
|
+
mqtt_publisher.publish_load_settings(
|
|
651
|
+
cpu_loader.get_num_threads(), cpu_loader.get_all_loads()
|
|
652
|
+
)
|
|
653
|
+
|
|
618
654
|
return {
|
|
619
655
|
"status": "success",
|
|
620
656
|
"num_threads": request.num_threads,
|
|
@@ -629,6 +665,13 @@ async def set_thread_load(thread_id: int, request: ThreadLoadRequest):
|
|
|
629
665
|
"""Set the CPU load for a specific thread."""
|
|
630
666
|
try:
|
|
631
667
|
cpu_loader.set_thread_load(thread_id, request.load_percent)
|
|
668
|
+
|
|
669
|
+
# Publish updated settings to MQTT
|
|
670
|
+
if mqtt_publisher:
|
|
671
|
+
mqtt_publisher.publish_load_settings(
|
|
672
|
+
cpu_loader.get_num_threads(), cpu_loader.get_all_loads()
|
|
673
|
+
)
|
|
674
|
+
|
|
632
675
|
return {
|
|
633
676
|
"status": "success",
|
|
634
677
|
"thread_id": thread_id,
|
|
@@ -644,6 +687,13 @@ async def set_all_thread_loads(request: AllThreadsLoadRequest):
|
|
|
644
687
|
"""Set the same CPU load for all threads."""
|
|
645
688
|
try:
|
|
646
689
|
cpu_loader.set_all_loads(request.load_percent)
|
|
690
|
+
|
|
691
|
+
# Publish updated settings to MQTT
|
|
692
|
+
if mqtt_publisher:
|
|
693
|
+
mqtt_publisher.publish_load_settings(
|
|
694
|
+
cpu_loader.get_num_threads(), cpu_loader.get_all_loads()
|
|
695
|
+
)
|
|
696
|
+
|
|
647
697
|
return {
|
|
648
698
|
"status": "success",
|
|
649
699
|
"load_percent": request.load_percent,
|
|
@@ -654,5 +704,74 @@ async def set_all_thread_loads(request: AllThreadsLoadRequest):
|
|
|
654
704
|
raise HTTPException(status_code=400, detail=str(e))
|
|
655
705
|
|
|
656
706
|
|
|
707
|
+
def parse_args():
|
|
708
|
+
"""Parse command-line arguments."""
|
|
709
|
+
parser = argparse.ArgumentParser(
|
|
710
|
+
description="CPU Loader - Generate controllable CPU load with REST API and WebUI"
|
|
711
|
+
)
|
|
712
|
+
parser.add_argument(
|
|
713
|
+
"--host",
|
|
714
|
+
default="0.0.0.0",
|
|
715
|
+
help="Host to bind the server to (default: 0.0.0.0)",
|
|
716
|
+
)
|
|
717
|
+
parser.add_argument(
|
|
718
|
+
"--port",
|
|
719
|
+
type=int,
|
|
720
|
+
default=8000,
|
|
721
|
+
help="Port to bind the server to (default: 8000)",
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
# MQTT arguments
|
|
725
|
+
mqtt_group = parser.add_argument_group("MQTT settings")
|
|
726
|
+
mqtt_group.add_argument(
|
|
727
|
+
"--mqtt-broker-host",
|
|
728
|
+
help="MQTT broker hostname (env: MQTT_BROKER_HOST)",
|
|
729
|
+
)
|
|
730
|
+
mqtt_group.add_argument(
|
|
731
|
+
"--mqtt-broker-port",
|
|
732
|
+
type=int,
|
|
733
|
+
help="MQTT broker port (env: MQTT_BROKER_PORT, default: 1883)",
|
|
734
|
+
)
|
|
735
|
+
mqtt_group.add_argument(
|
|
736
|
+
"--mqtt-username",
|
|
737
|
+
help="MQTT username (env: MQTT_USERNAME)",
|
|
738
|
+
)
|
|
739
|
+
mqtt_group.add_argument(
|
|
740
|
+
"--mqtt-password",
|
|
741
|
+
help="MQTT password (env: MQTT_PASSWORD)",
|
|
742
|
+
)
|
|
743
|
+
mqtt_group.add_argument(
|
|
744
|
+
"--mqtt-topic-prefix",
|
|
745
|
+
help="MQTT topic prefix (env: MQTT_TOPIC_PREFIX, default: cpu-loader)",
|
|
746
|
+
)
|
|
747
|
+
mqtt_group.add_argument(
|
|
748
|
+
"--mqtt-client-id",
|
|
749
|
+
help="MQTT client ID (env: MQTT_CLIENT_ID, default: cpu-loader)",
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
return parser.parse_args()
|
|
753
|
+
|
|
754
|
+
|
|
657
755
|
if __name__ == "__main__":
|
|
658
|
-
|
|
756
|
+
args = parse_args()
|
|
757
|
+
|
|
758
|
+
# Prepare MQTT arguments (only non-None values)
|
|
759
|
+
mqtt_args = {}
|
|
760
|
+
if args.mqtt_broker_host:
|
|
761
|
+
mqtt_args["broker_host"] = args.mqtt_broker_host
|
|
762
|
+
if args.mqtt_broker_port:
|
|
763
|
+
mqtt_args["broker_port"] = args.mqtt_broker_port
|
|
764
|
+
if args.mqtt_username:
|
|
765
|
+
mqtt_args["username"] = args.mqtt_username
|
|
766
|
+
if args.mqtt_password:
|
|
767
|
+
mqtt_args["password"] = args.mqtt_password
|
|
768
|
+
if args.mqtt_topic_prefix:
|
|
769
|
+
mqtt_args["topic_prefix"] = args.mqtt_topic_prefix
|
|
770
|
+
if args.mqtt_client_id:
|
|
771
|
+
mqtt_args["client_id"] = args.mqtt_client_id
|
|
772
|
+
|
|
773
|
+
# Store MQTT args in app state for lifespan to access
|
|
774
|
+
app.state.mqtt_args = mqtt_args
|
|
775
|
+
|
|
776
|
+
# Run the server
|
|
777
|
+
uvicorn.run(app, host=args.host, port=args.port)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MQTT Publisher Module
|
|
3
|
+
Publishes CPU load control settings and metrics to MQTT broker.
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from typing import Dict, Optional
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import paho.mqtt.client as mqtt
|
|
12
|
+
except ImportError:
|
|
13
|
+
mqtt = None
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MQTTPublisher:
|
|
19
|
+
"""Handles publishing CPU load data to MQTT broker."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
broker_host: Optional[str] = None,
|
|
24
|
+
broker_port: Optional[int] = None,
|
|
25
|
+
username: Optional[str] = None,
|
|
26
|
+
password: Optional[str] = None,
|
|
27
|
+
topic_prefix: Optional[str] = None,
|
|
28
|
+
client_id: Optional[str] = None,
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Initialize MQTT publisher.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
broker_host: MQTT broker hostname (env: MQTT_BROKER_HOST)
|
|
35
|
+
broker_port: MQTT broker port (env: MQTT_BROKER_PORT, default: 1883)
|
|
36
|
+
username: MQTT username (env: MQTT_USERNAME)
|
|
37
|
+
password: MQTT password (env: MQTT_PASSWORD)
|
|
38
|
+
topic_prefix: Topic prefix (env: MQTT_TOPIC_PREFIX, default: cpu-loader)
|
|
39
|
+
client_id: MQTT client ID (env: MQTT_CLIENT_ID, default: cpu-loader)
|
|
40
|
+
"""
|
|
41
|
+
if mqtt is None:
|
|
42
|
+
raise ImportError(
|
|
43
|
+
"paho-mqtt not installed. Install with: pip install paho-mqtt"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Get settings from arguments or environment variables
|
|
47
|
+
self.broker_host = broker_host or os.getenv("MQTT_BROKER_HOST")
|
|
48
|
+
self.broker_port = int(
|
|
49
|
+
broker_port or int(os.getenv("MQTT_BROKER_PORT", "1883"))
|
|
50
|
+
)
|
|
51
|
+
self.username = username or os.getenv("MQTT_USERNAME")
|
|
52
|
+
self.password = password or os.getenv("MQTT_PASSWORD")
|
|
53
|
+
self.topic_prefix = topic_prefix or os.getenv("MQTT_TOPIC_PREFIX", "cpu-loader")
|
|
54
|
+
self.client_id = client_id or os.getenv("MQTT_CLIENT_ID", "cpu-loader")
|
|
55
|
+
|
|
56
|
+
self.client: Optional[mqtt.Client] = None
|
|
57
|
+
self.connected = False
|
|
58
|
+
|
|
59
|
+
# Only connect if broker host is provided
|
|
60
|
+
if self.broker_host:
|
|
61
|
+
self._connect()
|
|
62
|
+
else:
|
|
63
|
+
logger.info("MQTT broker host not configured, MQTT publishing disabled")
|
|
64
|
+
|
|
65
|
+
def _connect(self):
|
|
66
|
+
"""Connect to MQTT broker."""
|
|
67
|
+
try:
|
|
68
|
+
# Create MQTT client
|
|
69
|
+
self.client = mqtt.Client(
|
|
70
|
+
client_id=self.client_id,
|
|
71
|
+
protocol=mqtt.MQTTv311,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Set callbacks
|
|
75
|
+
self.client.on_connect = self._on_connect
|
|
76
|
+
self.client.on_disconnect = self._on_disconnect
|
|
77
|
+
|
|
78
|
+
# Set credentials if provided
|
|
79
|
+
if self.username:
|
|
80
|
+
self.client.username_pw_set(self.username, self.password)
|
|
81
|
+
|
|
82
|
+
# Connect to broker
|
|
83
|
+
logger.info(
|
|
84
|
+
f"Connecting to MQTT broker at {self.broker_host}:{self.broker_port}"
|
|
85
|
+
)
|
|
86
|
+
self.client.connect(self.broker_host, self.broker_port, keepalive=60)
|
|
87
|
+
|
|
88
|
+
# Start network loop in background thread
|
|
89
|
+
self.client.loop_start()
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.error(f"Failed to connect to MQTT broker: {e}")
|
|
93
|
+
self.client = None
|
|
94
|
+
|
|
95
|
+
def _on_connect(self, client, userdata, flags, rc):
|
|
96
|
+
"""Callback when connected to MQTT broker."""
|
|
97
|
+
if rc == 0:
|
|
98
|
+
self.connected = True
|
|
99
|
+
logger.info("Connected to MQTT broker")
|
|
100
|
+
else:
|
|
101
|
+
self.connected = False
|
|
102
|
+
logger.error(f"Failed to connect to MQTT broker with code: {rc}")
|
|
103
|
+
|
|
104
|
+
def _on_disconnect(self, client, userdata, rc):
|
|
105
|
+
"""Callback when disconnected from MQTT broker."""
|
|
106
|
+
self.connected = False
|
|
107
|
+
if rc != 0:
|
|
108
|
+
logger.warning(f"Unexpected disconnect from MQTT broker: {rc}")
|
|
109
|
+
else:
|
|
110
|
+
logger.info("Disconnected from MQTT broker")
|
|
111
|
+
|
|
112
|
+
def publish_load_settings(self, num_threads: int, loads: Dict[int, float]):
|
|
113
|
+
"""
|
|
114
|
+
Publish load control settings to MQTT.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
num_threads: Number of active threads
|
|
118
|
+
loads: Dictionary mapping thread ID to load percentage
|
|
119
|
+
"""
|
|
120
|
+
if not self.connected or not self.client:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
# Calculate average load
|
|
125
|
+
avg_load = sum(loads.values()) / len(loads) if loads else 0.0
|
|
126
|
+
|
|
127
|
+
# Prepare payload
|
|
128
|
+
payload = {
|
|
129
|
+
"num_threads": num_threads,
|
|
130
|
+
"loads": loads,
|
|
131
|
+
"average_load": round(avg_load, 2),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Publish to topic
|
|
135
|
+
topic = f"{self.topic_prefix}/load_settings"
|
|
136
|
+
self.client.publish(
|
|
137
|
+
topic,
|
|
138
|
+
json.dumps(payload),
|
|
139
|
+
qos=1,
|
|
140
|
+
retain=True,
|
|
141
|
+
)
|
|
142
|
+
logger.debug(f"Published load settings to {topic}")
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"Failed to publish load settings: {e}")
|
|
146
|
+
|
|
147
|
+
def publish_cpu_metrics(self, total_cpu_percent: float, per_cpu_percent: list):
|
|
148
|
+
"""
|
|
149
|
+
Publish CPU metrics to MQTT.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
total_cpu_percent: Total CPU utilization percentage
|
|
153
|
+
per_cpu_percent: List of per-CPU utilization percentages
|
|
154
|
+
"""
|
|
155
|
+
if not self.connected or not self.client:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
# Prepare payload
|
|
160
|
+
payload = {
|
|
161
|
+
"total_cpu_percent": round(total_cpu_percent, 1),
|
|
162
|
+
"per_cpu_percent": [round(cpu, 1) for cpu in per_cpu_percent],
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Publish to topic
|
|
166
|
+
topic = f"{self.topic_prefix}/cpu_metrics"
|
|
167
|
+
self.client.publish(
|
|
168
|
+
topic,
|
|
169
|
+
json.dumps(payload),
|
|
170
|
+
qos=0,
|
|
171
|
+
retain=False,
|
|
172
|
+
)
|
|
173
|
+
logger.debug(f"Published CPU metrics to {topic}")
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(f"Failed to publish CPU metrics: {e}")
|
|
177
|
+
|
|
178
|
+
def disconnect(self):
|
|
179
|
+
"""Disconnect from MQTT broker."""
|
|
180
|
+
if self.client:
|
|
181
|
+
try:
|
|
182
|
+
self.client.loop_stop()
|
|
183
|
+
self.client.disconnect()
|
|
184
|
+
logger.info("Disconnected from MQTT broker")
|
|
185
|
+
except Exception as e:
|
|
186
|
+
logger.error(f"Error disconnecting from MQTT broker: {e}")
|
|
187
|
+
finally:
|
|
188
|
+
self.client = None
|
|
189
|
+
self.connected = False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|