v1.3.14: CPU每核MHz显示+四页测速(CPU运行速度/内存读写/硬盘读写/网络上下行)+最多5条历史自动删旧

This commit is contained in:
3970260761 committed 2026-11-08 00:59:09 +08:00
1 parent f417f8b01a
commit 1aff3138cb
4 files changed
+333 -8

No files matched your search

+174 -1
View File
@@ -16,7 +16,7 @@ PORT = int(os.environ.get("PORT", "7031"))
# ---------- CardAuth hub (免费形态: 安装统计/在线/公告/版本推送,失败开放) ----------
HUB_URL = os.environ.get("HUB_URL", "https://auth.88531.cn/api").rstrip("/")
HUB_PROJECT_ID = os.environ.get("HUB_PROJECT_ID", "12")
HUB_VERSION = os.environ.get("SITE_VERSION", "1.3.13")
HUB_VERSION = os.environ.get("SITE_VERSION", "1.3.14")
HUB = {"ok": False, "install_id": None, "latest": "", "update_available": False,
"update_required": False, "download_url": "", "changelog": "",
"announcements": [], "popup": [], "ts": 0}
@@ -374,6 +374,7 @@ def api_cpu():
return jsonify({"cpu": cache["cpu"], "load": cache["load"],
"active": active, "total": len(cache["procs"]),
"info": get_cpu_info(), "percpu": get_percpu(),
"mhz": get_percpu_mhz(),
"items": cache["procs"][:100]})
_cpu_static = {}
@@ -463,6 +464,178 @@ def get_percpu():
except Exception:
return []
def get_percpu_mhz():
"""每核实时频率(MHz): 优先读 cpufreq scaling_cur_freq,读不到回退 cpuinfo"""
mhz = []
try:
paths = sorted(glob.glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq/scaling_cur_freq"))
if not paths:
for base in ("/host/sys/devices/system/cpu", "/sys/devices/system/cpu"):
try:
names = sorted([d for d in os.listdir(base) if d.startswith("cpu") and d[3:].isdigit()])
except Exception:
continue
for n in names:
paths.append(os.path.join(base, n, "cpufreq/scaling_cur_freq"))
if paths:
break
for p in paths:
try:
with open(p) as f:
v = float(f.read().strip())
mhz.append(int(round(v / 1000.0)))
except Exception:
continue
except Exception:
pass
if not mhz:
try:
for cp in ("/host/proc/cpuinfo", "/proc/cpuinfo"):
try:
vals = []
with open(cp) as f:
for line in f:
if line.startswith("cpu MHz"):
vals.append(int(round(float(line.split(":")[1].strip()))))
if vals:
mhz = vals
break
except Exception:
continue
except Exception:
pass
return mhz
# ---------- 各页测速: CPU/内存/硬盘/网络(非破坏,失败返回error) ----------
import urllib.request as _urlreq
def _bench_cpu():
import math
N = 200000
t0 = time.perf_counter()
x = 0
for i in range(N):
x += (i * i) % 10007
dt = max(time.perf_counter() - t0, 1e-6)
return {"ms": int(round(dt * 1000)), "kops": int(round(N / dt / 1000)),
"score": int(round(N / dt / 1000)), "detail": "20万次整数运算耗时"}
def _bench_mem():
SZ = 32 * 1024 * 1024
t0 = time.perf_counter()
b = bytearray(SZ)
b[:] = b"\x5a" * SZ
dt_w = max(time.perf_counter() - t0, 1e-6)
t0 = time.perf_counter()
s = sum(b[::4096])
dt_r = max(time.perf_counter() - t0, 1e-6)
del b
return {"write_mbs": round(SZ / dt_w / 1048576, 1),
"read_mbs": round(SZ / dt_r / 1048576, 1),
"size_mb": 32, "detail": "32MB 内存写/读", "checksum": s % 997}
def _bench_disk():
SZ_MB = 32
data = os.urandom(4 * 1024 * 1024)
path = ""
for cand in ("/host/vol1/.fnos_resmon_bench.tmp",
"/host/.fnos_resmon_bench.tmp", "/tmp/.fnos_resmon_bench.tmp"):
try:
d = os.path.dirname(cand)
if os.path.isdir(d) and os.access(d, os.W_OK):
path = cand
break
except Exception:
continue
if not path:
return {"ok": False, "error": "无可写目录"}
try:
t0 = time.perf_counter()
with open(path, "wb") as f:
for _ in range(SZ_MB // 4):
f.write(data)
f.flush()
os.fsync(f.fileno())
dt_w = max(time.perf_counter() - t0, 1e-6)
t0 = time.perf_counter()
with open(path, "rb") as f:
while f.read(4 * 1024 * 1024):
pass
dt_r = max(time.perf_counter() - t0, 1e-6)
return {"write_mbs": round(SZ_MB / dt_w, 1),
"read_mbs": round(SZ_MB / dt_r, 1),
"size_mb": SZ_MB, "file": path.replace("/host", "") or "/",
"detail": "32MB 文件写(含落盘)/读"}
except Exception as e:
return {"ok": False, "error": str(e)}
finally:
try:
os.unlink(path)
except Exception:
pass
def _bench_net():
out = {}
try:
url = "https://speed.cloudflare.com/__down?bytes=5000000"
t0 = time.perf_counter()
req = _urlreq.Request(url, headers={"User-Agent": "fnos-resmon"})
with _urlreq.urlopen(req, timeout=25) as r:
n = 0
while True:
ch = r.read(256 * 1024)
if not ch:
break
n += len(ch)
dt = max(time.perf_counter() - t0, 1e-6)
out["down_mbps"] = round(n * 8 / dt / 1000000, 1)
out["down_mb"] = round(n / 1048576, 1)
out["down_s"] = round(dt, 1)
except Exception as e:
return {"ok": False, "error": "下载测速失败: %s" % e}
try:
payload = os.urandom(2 * 1024 * 1024)
t0 = time.perf_counter()
req = _urlreq.Request("https://speed.cloudflare.com/__up",
data=payload, method="POST",
headers={"Content-Type": "application/octet-stream"})
with _urlreq.urlopen(req, timeout=25) as r:
r.read()
dt = max(time.perf_counter() - t0, 1e-6)
out["up_mbps"] = round(len(payload) * 8 / dt / 1000000, 1)
out["up_mb"] = round(len(payload) / 1048576, 1)
out["up_s"] = round(dt, 1)
except Exception as e:
out["up_mbps"] = 0
out["up_error"] = "上传测速失败: %s" % e
out.update({"ok": True, "detail": "Cloudflare 测速点: 下行5MB/上行2MB"})
return out
@app.route("/api/bench/<kind>", methods=["POST"])
def api_bench(kind):
try:
if kind == "cpu":
r = _bench_cpu()
p = _read_perf()
r.update({"ok": True, "mode": p.get("mode", ""),
"governor": p.get("governor", ""), "epp": p.get("epp", ""),
"mhz_avg": (sum(get_percpu_mhz()) // len(get_percpu_mhz()) if get_percpu_mhz() else 0)})
elif kind == "mem":
r = _bench_mem()
r["ok"] = True
elif kind == "disk":
r = _bench_disk()
if "ok" not in r:
r["ok"] = True
elif kind == "net":
r = _bench_net()
else:
return jsonify({"ok": False, "error": "未知测速类型"}), 400
r["at"] = time.strftime("%m-%d %H:%M:%S", time.localtime())
return jsonify(r)
except Exception as e:
return jsonify({"ok": False, "error": str(e)}), 500
# ---------- CPU 性能模式: 省电/平衡/高性能(写宿主 cpufreq,失败开放) ----------
PERF_FILE = os.environ.get("PERF_FILE", "/run/secrets/perf_mode.json")
PERF_FILE_FALLBACKS = [PERF_FILE, "/app/perf_mode.json"]