cpu-loader 0.0.3__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.3/src/cpu_loader.egg-info → cpu_loader-0.1.2}/PKG-INFO +106 -2
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/README.md +104 -1
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/pyproject.toml +3 -2
- {cpu_loader-0.0.3 → cpu_loader-0.1.2/src/cpu_loader.egg-info}/PKG-INFO +106 -2
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/src/cpu_loader.egg-info/SOURCES.txt +1 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/src/cpu_loader.egg-info/requires.txt +1 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/src/cpu_loader.egg-info/top_level.txt +1 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/src/main.py +124 -5
- cpu_loader-0.1.2/src/mqtt_publisher.py +189 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/LICENSE +0 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/MANIFEST.in +0 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/setup.cfg +0 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/setup.py +0 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/src/cpu_loader.egg-info/dependency_links.txt +0 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/src/cpu_loader.egg-info/entry_points.txt +0 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/src/cpu_loader.py +0 -0
- {cpu_loader-0.0.3 → cpu_loader-0.1.2}/src/cpu_loader_core.c +0 -0
- {cpu_loader-0.0.3 → 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
|
|
@@ -42,7 +43,7 @@ Dynamic: license-file
|
|
|
42
43
|
[](https://pypi.org/project/cpu-loader/)
|
|
43
44
|
[](https://github.com/the78mole/cpu-loader/blob/main/LICENSE)
|
|
44
45
|
|
|
45
|
-
A
|
|
46
|
+
A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API.
|
|
46
47
|
|
|
47
48
|
## ✨ Key Features
|
|
48
49
|
|
|
@@ -53,6 +54,7 @@ A high-performance CPU load generator with **per-thread control** through an int
|
|
|
53
54
|
- **🚀 REST API**: Complete programmatic control for automation and testing
|
|
54
55
|
- **📱 Responsive Design**: Works seamlessly on desktop and mobile devices
|
|
55
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
|
|
56
58
|
|
|
57
59
|
## 🖼️ Screenshots
|
|
58
60
|
|
|
@@ -133,6 +135,22 @@ uv run src/main.py
|
|
|
133
135
|
|
|
134
136
|
The server will start on `http://localhost:8000`
|
|
135
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
|
+
|
|
136
154
|
### WebUI
|
|
137
155
|
|
|
138
156
|
Open your browser and navigate to `http://localhost:8000`
|
|
@@ -188,6 +206,88 @@ curl -X POST http://localhost:8000/api/threads \
|
|
|
188
206
|
|
|
189
207
|
Once the server is running, visit `http://localhost:8000/docs` for interactive API documentation powered by Swagger UI.
|
|
190
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
|
+
|
|
191
291
|
## Example Script
|
|
192
292
|
|
|
193
293
|
An example script (`src/example.py`) is provided to demonstrate programmatic control:
|
|
@@ -212,6 +312,7 @@ The example demonstrates:
|
|
|
212
312
|
- **src/cpu_loader_core.c**: High-performance C implementation using pthreads for CPU load generation
|
|
213
313
|
- **src/cpu_loader.py**: Python wrapper providing a clean API to the C extension
|
|
214
314
|
- **src/main.py**: FastAPI application with REST API and embedded WebUI
|
|
315
|
+
- **src/mqtt_publisher.py**: MQTT client for publishing metrics and settings
|
|
215
316
|
- **Threading Model**: Native pthreads for maximum efficiency and precise timing
|
|
216
317
|
- **Load Algorithm**: High-resolution busy-wait loops with nanosecond precision
|
|
217
318
|
|
|
@@ -221,6 +322,7 @@ The example demonstrates:
|
|
|
221
322
|
- FastAPI 0.115.0+
|
|
222
323
|
- Uvicorn 0.32.0+
|
|
223
324
|
- Pydantic 2.10.0+
|
|
325
|
+
- paho-mqtt 2.1.0+ (for MQTT publishing)
|
|
224
326
|
- C compiler (gcc or clang) for building the extension
|
|
225
327
|
|
|
226
328
|
## Use Cases
|
|
@@ -230,6 +332,8 @@ The example demonstrates:
|
|
|
230
332
|
- **Thermal Testing**: Check cooling system effectiveness
|
|
231
333
|
- **Power Consumption Analysis**: Measure power usage at different load levels
|
|
232
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
|
|
233
337
|
|
|
234
338
|
## Development
|
|
235
339
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://pypi.org/project/cpu-loader/)
|
|
6
6
|
[](https://github.com/the78mole/cpu-loader/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
-
A
|
|
8
|
+
A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API.
|
|
9
9
|
|
|
10
10
|
## ✨ Key Features
|
|
11
11
|
|
|
@@ -16,6 +16,7 @@ A high-performance CPU load generator with **per-thread control** through an int
|
|
|
16
16
|
- **🚀 REST API**: Complete programmatic control for automation and testing
|
|
17
17
|
- **📱 Responsive Design**: Works seamlessly on desktop and mobile devices
|
|
18
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
|
|
19
20
|
|
|
20
21
|
## 🖼️ Screenshots
|
|
21
22
|
|
|
@@ -96,6 +97,22 @@ uv run src/main.py
|
|
|
96
97
|
|
|
97
98
|
The server will start on `http://localhost:8000`
|
|
98
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
|
+
|
|
99
116
|
### WebUI
|
|
100
117
|
|
|
101
118
|
Open your browser and navigate to `http://localhost:8000`
|
|
@@ -151,6 +168,88 @@ curl -X POST http://localhost:8000/api/threads \
|
|
|
151
168
|
|
|
152
169
|
Once the server is running, visit `http://localhost:8000/docs` for interactive API documentation powered by Swagger UI.
|
|
153
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
|
+
|
|
154
253
|
## Example Script
|
|
155
254
|
|
|
156
255
|
An example script (`src/example.py`) is provided to demonstrate programmatic control:
|
|
@@ -175,6 +274,7 @@ The example demonstrates:
|
|
|
175
274
|
- **src/cpu_loader_core.c**: High-performance C implementation using pthreads for CPU load generation
|
|
176
275
|
- **src/cpu_loader.py**: Python wrapper providing a clean API to the C extension
|
|
177
276
|
- **src/main.py**: FastAPI application with REST API and embedded WebUI
|
|
277
|
+
- **src/mqtt_publisher.py**: MQTT client for publishing metrics and settings
|
|
178
278
|
- **Threading Model**: Native pthreads for maximum efficiency and precise timing
|
|
179
279
|
- **Load Algorithm**: High-resolution busy-wait loops with nanosecond precision
|
|
180
280
|
|
|
@@ -184,6 +284,7 @@ The example demonstrates:
|
|
|
184
284
|
- FastAPI 0.115.0+
|
|
185
285
|
- Uvicorn 0.32.0+
|
|
186
286
|
- Pydantic 2.10.0+
|
|
287
|
+
- paho-mqtt 2.1.0+ (for MQTT publishing)
|
|
187
288
|
- C compiler (gcc or clang) for building the extension
|
|
188
289
|
|
|
189
290
|
## Use Cases
|
|
@@ -193,6 +294,8 @@ The example demonstrates:
|
|
|
193
294
|
- **Thermal Testing**: Check cooling system effectiveness
|
|
194
295
|
- **Power Consumption Analysis**: Measure power usage at different load levels
|
|
195
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
|
|
196
299
|
|
|
197
300
|
## Development
|
|
198
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
|
|
@@ -42,7 +43,7 @@ Dynamic: license-file
|
|
|
42
43
|
[](https://pypi.org/project/cpu-loader/)
|
|
43
44
|
[](https://github.com/the78mole/cpu-loader/blob/main/LICENSE)
|
|
44
45
|
|
|
45
|
-
A
|
|
46
|
+
A tool to generate CPU load on a system with runtime configuration through a WebUI and REST API.
|
|
46
47
|
|
|
47
48
|
## ✨ Key Features
|
|
48
49
|
|
|
@@ -53,6 +54,7 @@ A high-performance CPU load generator with **per-thread control** through an int
|
|
|
53
54
|
- **🚀 REST API**: Complete programmatic control for automation and testing
|
|
54
55
|
- **📱 Responsive Design**: Works seamlessly on desktop and mobile devices
|
|
55
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
|
|
56
58
|
|
|
57
59
|
## 🖼️ Screenshots
|
|
58
60
|
|
|
@@ -133,6 +135,22 @@ uv run src/main.py
|
|
|
133
135
|
|
|
134
136
|
The server will start on `http://localhost:8000`
|
|
135
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
|
+
|
|
136
154
|
### WebUI
|
|
137
155
|
|
|
138
156
|
Open your browser and navigate to `http://localhost:8000`
|
|
@@ -188,6 +206,88 @@ curl -X POST http://localhost:8000/api/threads \
|
|
|
188
206
|
|
|
189
207
|
Once the server is running, visit `http://localhost:8000/docs` for interactive API documentation powered by Swagger UI.
|
|
190
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
|
+
|
|
191
291
|
## Example Script
|
|
192
292
|
|
|
193
293
|
An example script (`src/example.py`) is provided to demonstrate programmatic control:
|
|
@@ -212,6 +312,7 @@ The example demonstrates:
|
|
|
212
312
|
- **src/cpu_loader_core.c**: High-performance C implementation using pthreads for CPU load generation
|
|
213
313
|
- **src/cpu_loader.py**: Python wrapper providing a clean API to the C extension
|
|
214
314
|
- **src/main.py**: FastAPI application with REST API and embedded WebUI
|
|
315
|
+
- **src/mqtt_publisher.py**: MQTT client for publishing metrics and settings
|
|
215
316
|
- **Threading Model**: Native pthreads for maximum efficiency and precise timing
|
|
216
317
|
- **Load Algorithm**: High-resolution busy-wait loops with nanosecond precision
|
|
217
318
|
|
|
@@ -221,6 +322,7 @@ The example demonstrates:
|
|
|
221
322
|
- FastAPI 0.115.0+
|
|
222
323
|
- Uvicorn 0.32.0+
|
|
223
324
|
- Pydantic 2.10.0+
|
|
325
|
+
- paho-mqtt 2.1.0+ (for MQTT publishing)
|
|
224
326
|
- C compiler (gcc or clang) for building the extension
|
|
225
327
|
|
|
226
328
|
## Use Cases
|
|
@@ -230,6 +332,8 @@ The example demonstrates:
|
|
|
230
332
|
- **Thermal Testing**: Check cooling system effectiveness
|
|
231
333
|
- **Power Consumption Analysis**: Measure power usage at different load levels
|
|
232
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
|
|
233
337
|
|
|
234
338
|
## Development
|
|
235
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
|
|
|
@@ -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
|