cpu-loader 0.2.8__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cpu_loader-0.2.8/src/cpu_loader.egg-info → cpu_loader-0.3.0}/PKG-INFO +1 -1
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/pyproject.toml +2 -2
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader/main.py +71 -3
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader/mqtt_publisher.py +6 -1
- cpu_loader-0.3.0/src/cpu_loader/static/favicon.svg +34 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader/templates/index.html +23 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0/src/cpu_loader.egg-info}/PKG-INFO +1 -1
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader.egg-info/SOURCES.txt +1 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/LICENSE +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/MANIFEST.in +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/README.md +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/setup.cfg +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/setup.py +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader/__init__.py +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader/__main__.py +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader/cpu_loader.py +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader/cpu_loader_core.c +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader/example.py +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader.egg-info/dependency_links.txt +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader.egg-info/entry_points.txt +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/src/cpu_loader.egg-info/requires.txt +0 -0
- {cpu_loader-0.2.8 → cpu_loader-0.3.0}/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.
|
|
3
|
+
version = "0.3.0"
|
|
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"]
|
|
@@ -6,12 +6,14 @@ import argparse
|
|
|
6
6
|
import asyncio
|
|
7
7
|
import logging
|
|
8
8
|
from contextlib import asynccontextmanager
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
from typing import Dict, List, Optional, Set
|
|
10
11
|
|
|
11
12
|
import psutil
|
|
12
13
|
import uvicorn
|
|
13
14
|
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
|
|
14
|
-
from fastapi.responses import HTMLResponse
|
|
15
|
+
from fastapi.responses import HTMLResponse, FileResponse
|
|
16
|
+
from fastapi.staticfiles import StaticFiles
|
|
15
17
|
from pydantic import BaseModel, Field
|
|
16
18
|
|
|
17
19
|
from cpu_loader.cpu_loader import CPULoader
|
|
@@ -51,6 +53,7 @@ class ThreadsStatusResponse(BaseModel):
|
|
|
51
53
|
class CPUMetricsResponse(BaseModel):
|
|
52
54
|
total_cpu_percent: float
|
|
53
55
|
per_cpu_percent: List[float]
|
|
56
|
+
cpu_temperature: Optional[float] = None
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
# Global CPU loader instance, MQTT publisher, and WebSocket connections
|
|
@@ -58,6 +61,38 @@ cpu_loader = None
|
|
|
58
61
|
mqtt_publisher: Optional[MQTTPublisher] = None
|
|
59
62
|
websocket_connections: Set[WebSocket] = set()
|
|
60
63
|
monitoring_task = None
|
|
64
|
+
temperature_monitoring_enabled = True
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_cpu_temperature() -> Optional[float]:
|
|
68
|
+
"""Get CPU temperature if available and enabled."""
|
|
69
|
+
if not temperature_monitoring_enabled:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
# Try to get CPU temperature using psutil
|
|
74
|
+
temperatures = psutil.sensors_temperatures()
|
|
75
|
+
|
|
76
|
+
# Common temperature sensor names to check
|
|
77
|
+
temp_names = ['coretemp', 'cpu_thermal', 'acpi', 'k8temp', 'k10temp']
|
|
78
|
+
|
|
79
|
+
for temp_name in temp_names:
|
|
80
|
+
if temp_name in temperatures:
|
|
81
|
+
# Get the first temperature reading
|
|
82
|
+
temp_list = temperatures[temp_name]
|
|
83
|
+
if temp_list:
|
|
84
|
+
return round(temp_list[0].current, 1)
|
|
85
|
+
|
|
86
|
+
# If no specific sensor found, try the first available
|
|
87
|
+
for sensor_name, temp_list in temperatures.items():
|
|
88
|
+
if temp_list:
|
|
89
|
+
return round(temp_list[0].current, 1)
|
|
90
|
+
|
|
91
|
+
except (AttributeError, OSError, ImportError):
|
|
92
|
+
# Temperature monitoring not available on this system
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
return None
|
|
61
96
|
|
|
62
97
|
|
|
63
98
|
async def cpu_monitoring_loop():
|
|
@@ -73,12 +108,16 @@ async def cpu_monitoring_loop():
|
|
|
73
108
|
# Get CPU metrics (non-blocking after first call)
|
|
74
109
|
per_cpu = psutil.cpu_percent(interval=None, percpu=True)
|
|
75
110
|
total_cpu = sum(per_cpu) / len(per_cpu) if per_cpu else 0.0
|
|
111
|
+
|
|
112
|
+
# Get CPU temperature if available
|
|
113
|
+
cpu_temp = get_cpu_temperature()
|
|
76
114
|
|
|
77
115
|
# Prepare message
|
|
78
116
|
message = {
|
|
79
117
|
"type": "cpu_metrics",
|
|
80
118
|
"total_cpu_percent": round(total_cpu, 1),
|
|
81
119
|
"per_cpu_percent": [round(cpu, 1) for cpu in per_cpu],
|
|
120
|
+
"cpu_temperature": cpu_temp,
|
|
82
121
|
}
|
|
83
122
|
|
|
84
123
|
# Broadcast to all connected clients
|
|
@@ -95,7 +134,7 @@ async def cpu_monitoring_loop():
|
|
|
95
134
|
|
|
96
135
|
# Publish to MQTT if enabled
|
|
97
136
|
if mqtt_publisher:
|
|
98
|
-
mqtt_publisher.publish_cpu_metrics(total_cpu, per_cpu)
|
|
137
|
+
mqtt_publisher.publish_cpu_metrics(total_cpu, per_cpu, cpu_temp)
|
|
99
138
|
|
|
100
139
|
except Exception as e:
|
|
101
140
|
logger.error(f"Error in CPU monitoring loop: {e}")
|
|
@@ -142,6 +181,21 @@ app = FastAPI(
|
|
|
142
181
|
lifespan=lifespan,
|
|
143
182
|
)
|
|
144
183
|
|
|
184
|
+
# Mount static files
|
|
185
|
+
static_path = Path(__file__).parent / "static"
|
|
186
|
+
if static_path.exists():
|
|
187
|
+
app.mount("/static", StaticFiles(directory=static_path), name="static")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@app.get("/favicon.ico")
|
|
191
|
+
async def get_favicon():
|
|
192
|
+
"""Serve the favicon."""
|
|
193
|
+
favicon_path = Path(__file__).parent / "static" / "favicon.svg"
|
|
194
|
+
if favicon_path.exists():
|
|
195
|
+
return FileResponse(favicon_path, media_type="image/svg+xml")
|
|
196
|
+
else:
|
|
197
|
+
raise HTTPException(status_code=404, detail="Favicon not found")
|
|
198
|
+
|
|
145
199
|
|
|
146
200
|
@app.get("/", response_class=HTMLResponse)
|
|
147
201
|
async def get_webui():
|
|
@@ -182,7 +236,12 @@ async def get_cpu_metrics():
|
|
|
182
236
|
"""Get current CPU utilization metrics."""
|
|
183
237
|
per_cpu = psutil.cpu_percent(interval=0.1, percpu=True)
|
|
184
238
|
total_cpu = psutil.cpu_percent(interval=0)
|
|
185
|
-
|
|
239
|
+
cpu_temp = get_cpu_temperature()
|
|
240
|
+
return CPUMetricsResponse(
|
|
241
|
+
total_cpu_percent=total_cpu,
|
|
242
|
+
per_cpu_percent=per_cpu,
|
|
243
|
+
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,7 +364,11 @@ 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()
|
|
369
|
+
|
|
370
|
+
# Set temperature monitoring based on CLI argument
|
|
371
|
+
temperature_monitoring_enabled = not args.disable_temperature
|
|
304
372
|
|
|
305
373
|
# Prepare MQTT arguments (only non-None values)
|
|
306
374
|
mqtt_args = {}
|
|
@@ -144,13 +144,14 @@ class MQTTPublisher:
|
|
|
144
144
|
except Exception as e:
|
|
145
145
|
logger.error(f"Failed to publish load settings: {e}")
|
|
146
146
|
|
|
147
|
-
def publish_cpu_metrics(self, total_cpu_percent: float, per_cpu_percent: list):
|
|
147
|
+
def publish_cpu_metrics(self, total_cpu_percent: float, per_cpu_percent: list, cpu_temperature: Optional[float] = None):
|
|
148
148
|
"""
|
|
149
149
|
Publish CPU metrics to MQTT.
|
|
150
150
|
|
|
151
151
|
Args:
|
|
152
152
|
total_cpu_percent: Total CPU utilization percentage
|
|
153
153
|
per_cpu_percent: List of per-CPU utilization percentages
|
|
154
|
+
cpu_temperature: CPU temperature in Celsius (optional)
|
|
154
155
|
"""
|
|
155
156
|
if not self.connected or not self.client:
|
|
156
157
|
return
|
|
@@ -161,6 +162,10 @@ class MQTTPublisher:
|
|
|
161
162
|
"total_cpu_percent": round(total_cpu_percent, 1),
|
|
162
163
|
"per_cpu_percent": [round(cpu, 1) for cpu in per_cpu_percent],
|
|
163
164
|
}
|
|
165
|
+
|
|
166
|
+
# Add temperature if available
|
|
167
|
+
if cpu_temperature is not None:
|
|
168
|
+
payload["cpu_temperature"] = cpu_temperature
|
|
164
169
|
|
|
165
170
|
# Publish to topic
|
|
166
171
|
topic = f"{self.topic_prefix}/cpu_metrics"
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
</svg>
|
|
@@ -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
|
|
File without changes
|