i dont know what happend
This commit is contained in:
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
#mpv '/home/jonas/scripts/Rick Roll (Different link + no ads) [xvFZjo5PgG0].webm'
|
||||||
+6
-3
@@ -77,7 +77,7 @@ def menu():
|
|||||||
|
|
||||||
|
|
||||||
box("Available programs:")
|
box("Available programs:")
|
||||||
|
print("q: quit")
|
||||||
counter = 1
|
counter = 1
|
||||||
program_map = {}
|
program_map = {}
|
||||||
|
|
||||||
@@ -109,8 +109,10 @@ def menu():
|
|||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
choose = int(input("enter number of the programm you want: > "))
|
choose = input("enter number of the programm you want: > ")
|
||||||
chosen = program_map[choose]
|
if choose.lower() == "q":
|
||||||
|
sys.exit()
|
||||||
|
chosen = program_map[int(choose)]
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print("Es muss eine Nummer sein.")
|
print("Es muss eine Nummer sein.")
|
||||||
@@ -124,6 +126,7 @@ def menu():
|
|||||||
try:
|
try:
|
||||||
subprocess.run([sys.executable, chosen])
|
subprocess.run([sys.executable, chosen])
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
||||||
print("Interrupt signal recived...")
|
print("Interrupt signal recived...")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
# Simple statserver: CPU usage + CPU temp, chart on localhost:8080
|
||||||
|
#
|
||||||
|
# Every sample is stored in SQLite (stats.db, next to this file). Rows older
|
||||||
|
# than 7 days are purged, so the database always holds the last 7 days. A
|
||||||
|
# background thread samples once per second, independent of web clients.
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
from flask import Flask, request, jsonify, Response
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
DB_PATH = os.path.join(os.path.dirname(__file__), "stats.db")
|
||||||
|
SEVEN_DAYS = 7 * 24 * 3600
|
||||||
|
|
||||||
|
# name -> (span in seconds, bucket size in seconds); day/week rows are
|
||||||
|
# averaged into buckets so the payload stays small.
|
||||||
|
RANGES = {
|
||||||
|
"minute": (60, 1),
|
||||||
|
"hour": (3600, 10),
|
||||||
|
"day": (86400, 60),
|
||||||
|
"week": (SEVEN_DAYS, 600),
|
||||||
|
}
|
||||||
|
|
||||||
|
_prev_cpu = None
|
||||||
|
_last = {"usage": 0.0, "temp": None}
|
||||||
|
|
||||||
|
_db = sqlite3.connect(DB_PATH, check_same_thread=False)
|
||||||
|
_db_lock = threading.Lock()
|
||||||
|
_db.execute(
|
||||||
|
"CREATE TABLE IF NOT EXISTS samples ("
|
||||||
|
"ts REAL PRIMARY KEY, usage REAL, temp REAL)"
|
||||||
|
)
|
||||||
|
_db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def _read_temp():
|
||||||
|
"""Read CPU temperature from sysfs, preferring the CPU package sensor.
|
||||||
|
|
||||||
|
Returns degrees Celsius, or None if nothing readable is found.
|
||||||
|
"""
|
||||||
|
fallback = None
|
||||||
|
base = "/sys/class/thermal"
|
||||||
|
for zone in sorted(x for x in os.listdir(base) if x.startswith("thermal_zone")):
|
||||||
|
try:
|
||||||
|
with open(f"{base}/{zone}/type") as f:
|
||||||
|
ztype = f.read().strip().lower()
|
||||||
|
with open(f"{base}/{zone}/temp") as f:
|
||||||
|
temp = int(f.read().strip())
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
if ztype == "x86_pkg_temp": # package temp = best guess for CPU temp
|
||||||
|
return temp / 1000
|
||||||
|
if "cpu" in ztype or "pkg" in ztype or "core" in ztype:
|
||||||
|
fallback = fallback if fallback is not None else temp / 1000
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
|
||||||
|
def _read_usage():
|
||||||
|
"""CPU usage since the previous call, from /proc/stat. Returns percent."""
|
||||||
|
with open("/proc/stat") as f:
|
||||||
|
cpu_line = next(line for line in f if line.startswith("cpu "))
|
||||||
|
fields = [int(x) for x in cpu_line.split()[1:] if x.isdigit()]
|
||||||
|
idle, total = fields[3], sum(fields)
|
||||||
|
global _prev_cpu
|
||||||
|
prev = _prev_cpu
|
||||||
|
_prev_cpu = (idle, total)
|
||||||
|
if prev is None:
|
||||||
|
return None
|
||||||
|
didle = idle - prev[0]
|
||||||
|
dtotal = total - prev[1]
|
||||||
|
return 100.0 * (1 - didle / dtotal) if dtotal > 0 else 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def sample():
|
||||||
|
usage = _read_usage()
|
||||||
|
if usage is not None:
|
||||||
|
_last["usage"] = usage
|
||||||
|
_last["temp"] = _read_temp()
|
||||||
|
|
||||||
|
|
||||||
|
def _sampler():
|
||||||
|
"""Sample once per second and store it; purge >7-day-old rows every minute."""
|
||||||
|
n = 0
|
||||||
|
while True:
|
||||||
|
sample()
|
||||||
|
with _db_lock:
|
||||||
|
_db.execute(
|
||||||
|
"INSERT OR REPLACE INTO samples VALUES (?, ?, ?)",
|
||||||
|
(time.time(), _last["usage"], _last["temp"]),
|
||||||
|
)
|
||||||
|
# commit each insert so other connections (and restarts) see the
|
||||||
|
# data; a 1/sec commit is trivial load for SQLite
|
||||||
|
n += 1
|
||||||
|
if n % 60 == 0:
|
||||||
|
_db.execute("DELETE FROM samples WHERE ts < ?",
|
||||||
|
(time.time() - SEVEN_DAYS,))
|
||||||
|
_db.commit()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/stats")
|
||||||
|
def stats():
|
||||||
|
return jsonify(usage=_last["usage"], temp=_last["temp"])
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/history")
|
||||||
|
def history():
|
||||||
|
rng = request.args.get("range", "minute")
|
||||||
|
span, bucket = RANGES.get(rng, RANGES["minute"])
|
||||||
|
since = time.time() - span
|
||||||
|
with _db_lock:
|
||||||
|
rows = _db.execute(
|
||||||
|
"SELECT CAST(ts / ? AS INTEGER) * ? AS t, AVG(usage), AVG(temp) "
|
||||||
|
"FROM samples WHERE ts >= ? GROUP BY t ORDER BY t",
|
||||||
|
(bucket, bucket, since),
|
||||||
|
).fetchall()
|
||||||
|
return jsonify(points=[
|
||||||
|
{"t": round(r[0]), "u": r[1], "c": r[2]} for r in rows
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def index():
|
||||||
|
with open(os.path.join(os.path.dirname(__file__), "index.html"), "rb") as f:
|
||||||
|
return Response(f.read(), mimetype="text/html")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
threading.Thread(target=_sampler, daemon=True).start()
|
||||||
|
app.run(host="0.0.0.0", port=8080, threaded=True, use_reloader=False)
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>CPU Stats</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: system-ui, sans-serif; background: #111; color: #eee; margin: 2em; }
|
||||||
|
h1 { font-weight: 500; }
|
||||||
|
canvas { background: #1a1a1a; border-radius: 8px; display: block; }
|
||||||
|
.legend span { margin-right: 1.5em; }
|
||||||
|
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 5px; }
|
||||||
|
#now { font-size: 1.2em; color: #9cf; }
|
||||||
|
.ranges { margin: 1em 0; }
|
||||||
|
.ranges button {
|
||||||
|
font: inherit; color: #eee; background: #222; border: 1px solid #444;
|
||||||
|
border-radius: 6px; padding: 6px 14px; cursor: pointer; margin-right: 6px;
|
||||||
|
}
|
||||||
|
.ranges button.active { background: #356; border-color: #57f; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>CPU Stats <span id="now"></span></h1>
|
||||||
|
<div class="legend">
|
||||||
|
<span><span class="dot" style="background:#4cf"></span>Usage %</span>
|
||||||
|
<span><span class="dot" style="background:#fc4"></span>Temp °C</span>
|
||||||
|
</div>
|
||||||
|
<div class="ranges">
|
||||||
|
<button data-range="minute">1 min</button>
|
||||||
|
<button data-range="hour">1 h</button>
|
||||||
|
<button data-range="day">24 h</button>
|
||||||
|
<button data-range="week">7 days</button>
|
||||||
|
</div>
|
||||||
|
<canvas id="chart" width="800" height="320"></canvas>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// name -> span in seconds (must match app.py RANGES)
|
||||||
|
const SPANS = { minute: 60, hour: 3600, day: 86400, week: 7 * 86400 };
|
||||||
|
// How often to reload the chart for each range
|
||||||
|
const RELOAD_MS = { minute: 1000, hour: 5000, day: 30000, week: 60000 };
|
||||||
|
|
||||||
|
const state = { range: 'minute', points: [] };
|
||||||
|
const canvas = document.getElementById('chart');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const nowEl = document.getElementById('now');
|
||||||
|
const rangeButtons = document.querySelectorAll('.ranges button');
|
||||||
|
|
||||||
|
function setRange(range) {
|
||||||
|
state.range = range;
|
||||||
|
rangeButtons.forEach(b => b.classList.toggle('active', b.dataset.range === range));
|
||||||
|
setRangeButtonsEnabled(false); // while loading first data
|
||||||
|
if (setRange._timer) clearInterval(setRange._timer);
|
||||||
|
setRange._timer = setInterval(load, RELOAD_MS[range]);
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRangeButtonsEnabled(enabled) {
|
||||||
|
rangeButtons.forEach(b => b.disabled = !enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
try {
|
||||||
|
const { points } = await (await fetch('/api/history?range=' + state.range)).json();
|
||||||
|
state.points = points;
|
||||||
|
setRangeButtonsEnabled(true);
|
||||||
|
const last = points[points.length - 1];
|
||||||
|
if (last && last.u != null) {
|
||||||
|
nowEl.textContent = `- ${last.u.toFixed(0)}% / ${
|
||||||
|
last.c == null ? '?' : last.c.toFixed(1)
|
||||||
|
}°C`;
|
||||||
|
} else {
|
||||||
|
nowEl.textContent = last ? '—' : ' (no data yet)';
|
||||||
|
}
|
||||||
|
draw();
|
||||||
|
} catch (err) {
|
||||||
|
nowEl.textContent = '(waiting for server…)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
// grid
|
||||||
|
ctx.strokeStyle = '#333';
|
||||||
|
for (let i = 1; i < 4; i++) {
|
||||||
|
const y = (canvas.height / 4) * i;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, y);
|
||||||
|
ctx.lineTo(canvas.width, y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
drawLine(p => p.u, v => v / 100, '#4cf');
|
||||||
|
drawLine(p => p.c, v => (v == null ? null : v / 100), '#fc4'); // scale 0–100 °C
|
||||||
|
}
|
||||||
|
|
||||||
|
// x axis: fixed window ending "now", so older data scrolls off the left
|
||||||
|
function drawLine(get, scale, color) {
|
||||||
|
const span = SPANS[state.range];
|
||||||
|
const t1 = Date.now() / 1000, t0 = t1 - span;
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
let started = false;
|
||||||
|
state.points.forEach(p => {
|
||||||
|
const v = get(p);
|
||||||
|
const s = v == null ? null : scale(v);
|
||||||
|
if (s == null) return;
|
||||||
|
const x = (p.t - t0) / span * (canvas.width - 10) + 5;
|
||||||
|
const y = canvas.height - s * (canvas.height - 10) - 5;
|
||||||
|
if (!started) { ctx.moveTo(x, y); started = true; }
|
||||||
|
else ctx.lineTo(x, y);
|
||||||
|
});
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
rangeButtons.forEach(b => b.addEventListener('click', () => setRange(b.dataset.range)));
|
||||||
|
setRange('minute');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user