cpu-loader 0.2.8__tar.gz → 0.4.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.2.8/src/cpu_loader.egg-info → cpu_loader-0.4.2}/PKG-INFO +1 -1
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/pyproject.toml +2 -2
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader/cpu_loader.py +1 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader/main.py +71 -3
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader/mqtt_publisher.py +12 -1
- cpu_loader-0.4.2/src/cpu_loader/static/favicon.svg +38 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader/templates/index.html +23 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2/src/cpu_loader.egg-info}/PKG-INFO +1 -1
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader.egg-info/SOURCES.txt +1 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/LICENSE +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/MANIFEST.in +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/README.md +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/setup.cfg +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/setup.py +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader/__init__.py +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader/__main__.py +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader/cpu_loader_core.c +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader/example.py +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader.egg-info/dependency_links.txt +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader.egg-info/entry_points.txt +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader.egg-info/requires.txt +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.4.2}/src/cpu_loader.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cpu-loader"
|
|
3
|
-
version = "0.2
|
|
3
|
+
version = "0.4.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"
|
|
@@ -53,4 +53,4 @@ package-dir = {"" = "src"}
|
|
|
53
53
|
packages = ["cpu_loader"]
|
|
54
54
|
|
|
55
55
|
[tool.setuptools.package-data]
|
|
56
|
-
cpu_loader = ["templates/*.html", "*.c"]
|
|
56
|
+
cpu_loader = ["templates/*.html", "static/*", "*.c"]
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
CPU Loader FastAPI Application
|
|
3
3
|
Provides REST API and WebUI for controlling CPU load.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import argparse
|
|
6
7
|
import asyncio
|
|
7
8
|
import logging
|
|
8
9
|
from contextlib import asynccontextmanager
|
|
10
|
+
from pathlib import Path
|
|
9
11
|
from typing import Dict, List, Optional, Set
|
|
10
12
|
|
|
11
13
|
import psutil
|
|
12
14
|
import uvicorn
|
|
13
15
|
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
|
|
14
|
-
from fastapi.responses import HTMLResponse
|
|
16
|
+
from fastapi.responses import FileResponse, HTMLResponse
|
|
17
|
+
from fastapi.staticfiles import StaticFiles
|
|
15
18
|
from pydantic import BaseModel, Field
|
|
16
19
|
|
|
17
20
|
from cpu_loader.cpu_loader import CPULoader
|
|
@@ -51,6 +54,7 @@ class ThreadsStatusResponse(BaseModel):
|
|
|
51
54
|
class CPUMetricsResponse(BaseModel):
|
|
52
55
|
total_cpu_percent: float
|
|
53
56
|
per_cpu_percent: List[float]
|
|
57
|
+
cpu_temperature: Optional[float] = None
|
|
54
58
|
|
|
55
59
|
|
|
56
60
|
# Global CPU loader instance, MQTT publisher, and WebSocket connections
|
|
@@ -58,6 +62,39 @@ cpu_loader = None
|
|
|
58
62
|
mqtt_publisher: Optional[MQTTPublisher] = None
|
|
59
63
|
websocket_connections: Set[WebSocket] = set()
|
|
60
64
|
monitoring_task = None
|
|
65
|
+
temperature_monitoring_enabled = True
|
|
66
|
+
temperature_monitoring_enabled = True
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_cpu_temperature() -> Optional[float]:
|
|
70
|
+
"""Get CPU temperature if available and enabled."""
|
|
71
|
+
if not temperature_monitoring_enabled:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# Try to get CPU temperature using psutil
|
|
76
|
+
temperatures = psutil.sensors_temperatures()
|
|
77
|
+
|
|
78
|
+
# Common temperature sensor names to check
|
|
79
|
+
temp_names = ["coretemp", "cpu_thermal", "acpi", "k8temp", "k10temp"]
|
|
80
|
+
|
|
81
|
+
for temp_name in temp_names:
|
|
82
|
+
if temp_name in temperatures:
|
|
83
|
+
# Get the first temperature reading
|
|
84
|
+
temp_list = temperatures[temp_name]
|
|
85
|
+
if temp_list:
|
|
86
|
+
return round(temp_list[0].current, 1)
|
|
87
|
+
|
|
88
|
+
# If no specific sensor found, try the first available
|
|
89
|
+
for sensor_name, temp_list in temperatures.items():
|
|
90
|
+
if temp_list:
|
|
91
|
+
return round(temp_list[0].current, 1)
|
|
92
|
+
|
|
93
|
+
except (AttributeError, OSError, ImportError):
|
|
94
|
+
# Temperature monitoring not available on this system
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
return None
|
|
61
98
|
|
|
62
99
|
|
|
63
100
|
async def cpu_monitoring_loop():
|
|
@@ -74,11 +111,15 @@ async def cpu_monitoring_loop():
|
|
|
74
111
|
per_cpu = psutil.cpu_percent(interval=None, percpu=True)
|
|
75
112
|
total_cpu = sum(per_cpu) / len(per_cpu) if per_cpu else 0.0
|
|
76
113
|
|
|
114
|
+
# Get CPU temperature if available
|
|
115
|
+
cpu_temp = get_cpu_temperature()
|
|
116
|
+
|
|
77
117
|
# Prepare message
|
|
78
118
|
message = {
|
|
79
119
|
"type": "cpu_metrics",
|
|
80
120
|
"total_cpu_percent": round(total_cpu, 1),
|
|
81
121
|
"per_cpu_percent": [round(cpu, 1) for cpu in per_cpu],
|
|
122
|
+
"cpu_temperature": cpu_temp,
|
|
82
123
|
}
|
|
83
124
|
|
|
84
125
|
# Broadcast to all connected clients
|
|
@@ -95,7 +136,7 @@ async def cpu_monitoring_loop():
|
|
|
95
136
|
|
|
96
137
|
# Publish to MQTT if enabled
|
|
97
138
|
if mqtt_publisher:
|
|
98
|
-
mqtt_publisher.publish_cpu_metrics(total_cpu, per_cpu)
|
|
139
|
+
mqtt_publisher.publish_cpu_metrics(total_cpu, per_cpu, cpu_temp)
|
|
99
140
|
|
|
100
141
|
except Exception as e:
|
|
101
142
|
logger.error(f"Error in CPU monitoring loop: {e}")
|
|
@@ -142,6 +183,21 @@ app = FastAPI(
|
|
|
142
183
|
lifespan=lifespan,
|
|
143
184
|
)
|
|
144
185
|
|
|
186
|
+
# Mount static files
|
|
187
|
+
static_path = Path(__file__).parent / "static"
|
|
188
|
+
if static_path.exists():
|
|
189
|
+
app.mount("/static", StaticFiles(directory=static_path), name="static")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@app.get("/favicon.ico")
|
|
193
|
+
async def get_favicon():
|
|
194
|
+
"""Serve the favicon."""
|
|
195
|
+
favicon_path = Path(__file__).parent / "static" / "favicon.svg"
|
|
196
|
+
if favicon_path.exists():
|
|
197
|
+
return FileResponse(favicon_path, media_type="image/svg+xml")
|
|
198
|
+
else:
|
|
199
|
+
raise HTTPException(status_code=404, detail="Favicon not found")
|
|
200
|
+
|
|
145
201
|
|
|
146
202
|
@app.get("/", response_class=HTMLResponse)
|
|
147
203
|
async def get_webui():
|
|
@@ -182,7 +238,10 @@ async def get_cpu_metrics():
|
|
|
182
238
|
"""Get current CPU utilization metrics."""
|
|
183
239
|
per_cpu = psutil.cpu_percent(interval=0.1, percpu=True)
|
|
184
240
|
total_cpu = psutil.cpu_percent(interval=0)
|
|
185
|
-
|
|
241
|
+
cpu_temp = get_cpu_temperature()
|
|
242
|
+
return CPUMetricsResponse(
|
|
243
|
+
total_cpu_percent=total_cpu, per_cpu_percent=per_cpu, cpu_temperature=cpu_temp
|
|
244
|
+
)
|
|
186
245
|
|
|
187
246
|
|
|
188
247
|
@app.post("/api/threads")
|
|
@@ -266,6 +325,11 @@ def parse_args():
|
|
|
266
325
|
default=8000,
|
|
267
326
|
help="Port to bind the server to (default: 8000)",
|
|
268
327
|
)
|
|
328
|
+
parser.add_argument(
|
|
329
|
+
"--disable-temperature",
|
|
330
|
+
action="store_true",
|
|
331
|
+
help="Disable CPU temperature monitoring (useful if temperature sensors are unavailable)",
|
|
332
|
+
)
|
|
269
333
|
|
|
270
334
|
# MQTT arguments
|
|
271
335
|
mqtt_group = parser.add_argument_group("MQTT settings")
|
|
@@ -300,8 +364,12 @@ def parse_args():
|
|
|
300
364
|
|
|
301
365
|
def run():
|
|
302
366
|
"""Entry point for the CPU Loader application."""
|
|
367
|
+
global temperature_monitoring_enabled
|
|
303
368
|
args = parse_args()
|
|
304
369
|
|
|
370
|
+
# Set temperature monitoring based on CLI argument
|
|
371
|
+
temperature_monitoring_enabled = not args.disable_temperature
|
|
372
|
+
|
|
305
373
|
# Prepare MQTT arguments (only non-None values)
|
|
306
374
|
mqtt_args = {}
|
|
307
375
|
if args.mqtt_broker_host:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
MQTT Publisher Module
|
|
3
3
|
Publishes CPU load control settings and metrics to MQTT broker.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import json
|
|
6
7
|
import logging
|
|
7
8
|
import os
|
|
@@ -144,13 +145,19 @@ class MQTTPublisher:
|
|
|
144
145
|
except Exception as e:
|
|
145
146
|
logger.error(f"Failed to publish load settings: {e}")
|
|
146
147
|
|
|
147
|
-
def publish_cpu_metrics(
|
|
148
|
+
def publish_cpu_metrics(
|
|
149
|
+
self,
|
|
150
|
+
total_cpu_percent: float,
|
|
151
|
+
per_cpu_percent: list,
|
|
152
|
+
cpu_temperature: Optional[float] = None,
|
|
153
|
+
):
|
|
148
154
|
"""
|
|
149
155
|
Publish CPU metrics to MQTT.
|
|
150
156
|
|
|
151
157
|
Args:
|
|
152
158
|
total_cpu_percent: Total CPU utilization percentage
|
|
153
159
|
per_cpu_percent: List of per-CPU utilization percentages
|
|
160
|
+
cpu_temperature: CPU temperature in Celsius (optional)
|
|
154
161
|
"""
|
|
155
162
|
if not self.connected or not self.client:
|
|
156
163
|
return
|
|
@@ -162,6 +169,10 @@ class MQTTPublisher:
|
|
|
162
169
|
"per_cpu_percent": [round(cpu, 1) for cpu in per_cpu_percent],
|
|
163
170
|
}
|
|
164
171
|
|
|
172
|
+
# Add temperature if available
|
|
173
|
+
if cpu_temperature is not None:
|
|
174
|
+
payload["cpu_temperature"] = cpu_temperature
|
|
175
|
+
|
|
165
176
|
# Publish to topic
|
|
166
177
|
topic = f"{self.topic_prefix}/cpu_metrics"
|
|
167
178
|
self.client.publish(
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="cpuGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
|
|
5
|
+
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<!-- CPU chip outline -->
|
|
9
|
+
<rect x="4" y="4" width="24" height="24" rx="2" ry="2" fill="url(#cpuGradient)" stroke="#333" stroke-width="1"/>
|
|
10
|
+
<!-- CPU pins -->
|
|
11
|
+
<rect x="1" y="8" width="2" height="2" fill="#333"/>
|
|
12
|
+
<rect x="1" y="12" width="2" height="2" fill="#333"/>
|
|
13
|
+
<rect x="1" y="16" width="2" height="2" fill="#333"/>
|
|
14
|
+
<rect x="1" y="20" width="2" height="2" fill="#333"/>
|
|
15
|
+
<rect x="29" y="8" width="2" height="2" fill="#333"/>
|
|
16
|
+
<rect x="29" y="12" width="2" height="2" fill="#333"/>
|
|
17
|
+
<rect x="29" y="16" width="2" height="2" fill="#333"/>
|
|
18
|
+
<rect x="29" y="20" width="2" height="2" fill="#333"/>
|
|
19
|
+
<rect x="8" y="1" width="2" height="2" fill="#333"/>
|
|
20
|
+
<rect x="12" y="1" width="2" height="2" fill="#333"/>
|
|
21
|
+
<rect x="16" y="1" width="2" height="2" fill="#333"/>
|
|
22
|
+
<rect x="20" y="1" width="2" height="2" fill="#333"/>
|
|
23
|
+
<rect x="8" y="29" width="2" height="2" fill="#333"/>
|
|
24
|
+
<rect x="12" y="29" width="2" height="2" fill="#333"/>
|
|
25
|
+
<rect x="16" y="29" width="2" height="2" fill="#333"/>
|
|
26
|
+
<rect x="20" y="29" width="2" height="2" fill="#333"/>
|
|
27
|
+
<!-- CPU core -->
|
|
28
|
+
<rect x="8" y="8" width="16" height="16" rx="1" ry="1" fill="rgba(255,255,255,0.2)"/>
|
|
29
|
+
<!-- Load indicator bars -->
|
|
30
|
+
<rect x="10" y="12" width="2" height="8" fill="#4CAF50"/>
|
|
31
|
+
<rect x="13" y="14" width="2" height="6" fill="#FFC107"/>
|
|
32
|
+
<rect x="16" y="10" width="2" height="10" fill="#F44336"/>
|
|
33
|
+
<rect x="19" y="16" width="2" height="4" fill="#2196F3"/>
|
|
34
|
+
<<<<<<< HEAD
|
|
35
|
+
</svg>
|
|
36
|
+
=======
|
|
37
|
+
</svg>
|
|
38
|
+
>>>>>>> 0b500ee (feat: add CPU temperature monitoring and update related UI components)
|
|
@@ -214,6 +214,15 @@
|
|
|
214
214
|
<div class="cpu-bar total" id="total-cpu-bar" style="width: 0%"></div>
|
|
215
215
|
</div>
|
|
216
216
|
</div>
|
|
217
|
+
<div class="cpu-bar-container" id="cpu-temperature-container" style="display: none;">
|
|
218
|
+
<div class="cpu-bar-label">
|
|
219
|
+
<span><strong>🌡️ CPU Temperature</strong></span>
|
|
220
|
+
<span id="cpu-temperature-value">-</span>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="cpu-bar-wrapper">
|
|
223
|
+
<div class="cpu-bar" id="cpu-temperature-bar" style="width: 0%; background: linear-gradient(90deg, #2196F3 0%, #4CAF50 25%, #FFC107 60%, #FF5722 85%, #B71C1C 100%);"></div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
217
226
|
<div id="per-cpu-bars"></div>
|
|
218
227
|
</div>
|
|
219
228
|
|
|
@@ -274,6 +283,20 @@
|
|
|
274
283
|
document.getElementById('total-cpu-value').textContent = `${totalCpu}%`;
|
|
275
284
|
document.getElementById('total-cpu-bar').style.width = `${totalCpu}%`;
|
|
276
285
|
|
|
286
|
+
// Update CPU temperature if available
|
|
287
|
+
if (data.cpu_temperature !== null && data.cpu_temperature !== undefined) {
|
|
288
|
+
const tempContainer = document.getElementById('cpu-temperature-container');
|
|
289
|
+
const tempValue = document.getElementById('cpu-temperature-value');
|
|
290
|
+
const tempBar = document.getElementById('cpu-temperature-bar');
|
|
291
|
+
|
|
292
|
+
tempContainer.style.display = 'block';
|
|
293
|
+
tempValue.textContent = `${data.cpu_temperature}°C`;
|
|
294
|
+
|
|
295
|
+
// Scale temperature (0-100°C -> 0-100%)
|
|
296
|
+
const tempPercent = Math.min(100, Math.max(0, data.cpu_temperature));
|
|
297
|
+
tempBar.style.width = `${tempPercent}%`;
|
|
298
|
+
}
|
|
299
|
+
|
|
277
300
|
// Update per-CPU bars
|
|
278
301
|
const perCpuContainer = document.getElementById('per-cpu-bars');
|
|
279
302
|
if (data.per_cpu_percent.length !== numCPUs) {
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|