Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a430e3a791 | ||
|
|
eab6475199 |
No files matched your search
@@ -558,6 +558,83 @@ def _df_host():
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
@app.route("/api/cpu/advise")
|
||||
def api_cpu_advise():
|
||||
"""负载诊断:档位结论 + 能否顶住 + 优化建议"""
|
||||
try:
|
||||
load = list(os.getloadavg())
|
||||
except Exception:
|
||||
load = [0, 0, 0]
|
||||
try:
|
||||
cpu_pct = psutil.cpu_percent(interval=None)
|
||||
except Exception:
|
||||
cpu_pct = 0
|
||||
try:
|
||||
vm = psutil.virtual_memory()
|
||||
except Exception:
|
||||
vm = None
|
||||
logical = psutil.cpu_count() or 1
|
||||
l1 = load[0]
|
||||
per_core = l1 / logical
|
||||
if per_core < 0.5 and (cpu_pct or 0) < 50:
|
||||
level, verdict = "轻松", "✅ 当前配置完全顶得住,负载很轻,还有大量余量。"
|
||||
elif per_core < 0.8 and (cpu_pct or 0) < 75:
|
||||
level, verdict = "正常", "✅ 当前配置顶得住,负载正常,日常使用流畅。"
|
||||
elif per_core < 1.0 and (cpu_pct or 0) < 90:
|
||||
level, verdict = "偏高", "⚠️ 当前配置勉强顶住,高峰期可能卡顿,建议按下面方法优化。"
|
||||
else:
|
||||
level, verdict = "过载", "🔴 当前配置顶不住了!负载已超过 CPU 承受能力,请立即优化或升级。"
|
||||
tips = []
|
||||
tops = sorted(cache.get("procs", []), key=lambda x: x.get("cpu", 0), reverse=True)[:3]
|
||||
if tops and (tops[0].get("cpu") or 0) > 50:
|
||||
tips.append("🎯 头号吃CPU进程是 %s(占用 %s%%,PID %s),如非必需请结束它,效果最明显。" % (
|
||||
tops[0].get("name"), tops[0].get("cpu"), tops[0].get("pid")))
|
||||
if vm is not None and vm.percent > 85:
|
||||
tips.append("💾 内存已用 %s%%,内存不足会拖慢 CPU(频繁 swap)。先点内存页「一键回收」,或加内存条。" % vm.percent)
|
||||
tips.append("🧹 点下方「一键优化系统」:自动数据落盘 + 回收内存缓存 + 内存碎片整理,1 分钟见效。")
|
||||
tips.append("📦 精简开机自启与常驻 Docker 容器,不用的先停掉(docker stop),负载立降。")
|
||||
tips.append("🌡️ 检查散热:CPU 过热会自动降频,清理灰尘、保证通风,频率上去了自然流畅。")
|
||||
return jsonify({"load": [round(x, 2) for x in load], "cpu": round(cpu_pct or 0, 1),
|
||||
"logical": logical, "per_core": round(per_core, 2),
|
||||
"level": level, "verdict": verdict, "tips": tips,
|
||||
"top_cpu": [{"name": t.get("name"), "cpu": t.get("cpu"), "pid": t.get("pid")} for t in tops]})
|
||||
|
||||
@app.route("/api/sys/optimize", methods=["POST"])
|
||||
def api_sys_optimize():
|
||||
"""一键优化系统: drop caches + sync + compact(若支持),返回前后对比"""
|
||||
steps = []
|
||||
try:
|
||||
before_mem = psutil.virtual_memory().available
|
||||
except Exception:
|
||||
before_mem = 0
|
||||
try:
|
||||
subprocess.run(["sync"], check=False, timeout=15)
|
||||
steps.append("sync 数据落盘 ✅")
|
||||
except Exception as e:
|
||||
steps.append("sync 失败: %s" % e)
|
||||
try:
|
||||
p = os.environ.get("PROCFS_PATH", "/proc")
|
||||
with open(os.path.join(p, "sys", "vm", "drop_caches"), "w") as f:
|
||||
f.write("3")
|
||||
steps.append("回收内存缓存 ✅")
|
||||
except Exception as e:
|
||||
steps.append("回收缓存失败: %s" % e)
|
||||
try:
|
||||
cp = os.path.join(os.environ.get("PROCFS_PATH", "/proc"), "sys", "vm", "compact_memory")
|
||||
if os.path.exists(cp):
|
||||
with open(cp, "w") as f:
|
||||
f.write("1")
|
||||
steps.append("内存碎片整理 ✅")
|
||||
except Exception as e:
|
||||
steps.append("碎片整理跳过: %s" % e)
|
||||
try:
|
||||
after_mem = psutil.virtual_memory().available
|
||||
except Exception:
|
||||
after_mem = 0
|
||||
return jsonify({"ok": True, "steps": steps,
|
||||
"freed": max(0, after_mem - before_mem),
|
||||
"available": after_mem})
|
||||
|
||||
@app.route("/api/mem/drop", methods=["POST"])
|
||||
def api_mem_drop():
|
||||
import subprocess
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 316 KiB |
+15
-3
@@ -56,13 +56,16 @@ button.refresh{background:#20a53a;color:#fff;border:none;border-radius:3px;paddi
|
||||
<div class="tab" data-t="disk">磁盘</div>
|
||||
<div class="tab" data-t="net">网络</div>
|
||||
<div style="margin-left:auto;display:flex;align-items:center;gap:12px;padding-right:16px;font-size:12px">
|
||||
<span style="color:#999">v1.2.8</span>
|
||||
<span style="color:#999">v1.3.3</span>
|
||||
<label style="color:#999">刷新 <select id="ival" onchange="setIval(this.value)" style="border:1px solid #ddd;border-radius:3px;padding:2px 4px;color:#333"><option value="3">3s</option><option value="5">5s</option><option value="10">10s</option><option value="30">30s</option><option value="60">60s</option></select></label>
|
||||
<button id="pauseBtn" onclick="togglePause()" style="background:#ff9900;color:#fff;border:none;border-radius:3px;padding:3px 10px;cursor:pointer">⏸ 停止刷新</button>
|
||||
<a href="https://nav.88531.cn" target="_blank" style="color:#20a53a;text-decoration:none">资享导航站</a>
|
||||
<a href="https://gitea.88531.cn/jinghui/fnos-resmon" target="_blank" style="color:#20a53a;text-decoration:none">开源地址</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" id="summary"></div>
|
||||
<div class="cpuinfo" id="cpuinfo" style="display:none"></div>
|
||||
<div class="cpuinfo" id="cpuadvise" style="display:none"></div>
|
||||
<div class="cpuinfo" id="meminfo" style="display:none"></div>
|
||||
<div class="cpuinfo" id="diskinfo" style="display:none"></div>
|
||||
<div class="cpuinfo" id="netinfo" style="display:none"></div>
|
||||
@@ -76,11 +79,14 @@ button.refresh{background:#20a53a;color:#fff;border:none;border-radius:3px;paddi
|
||||
let cur='cpu';
|
||||
let sortState={key:null,dir:-1};
|
||||
let curData=null;
|
||||
document.querySelectorAll('.tab').forEach(t=>t.onclick=()=>{document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));t.classList.add('active');cur=t.dataset.t;document.getElementById('disks').innerHTML='';const ci=document.getElementById('cpuinfo');if(ci)ci.style.display='none';const mi2=document.getElementById('meminfo');if(mi2)mi2.style.display='none';const di=document.getElementById('diskinfo');if(di)di.style.display='none';const ni=document.getElementById('netinfo');if(ni)ni.style.display='none';load(true)});
|
||||
document.querySelectorAll('.tab').forEach(t=>t.onclick=()=>{document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));t.classList.add('active');cur=t.dataset.t;document.getElementById('disks').innerHTML='';const ci=document.getElementById('cpuinfo');if(ci)ci.style.display='none';const ca=document.getElementById('cpuadvise');if(ca)ca.style.display='none';const mi2=document.getElementById('meminfo');if(mi2)mi2.style.display='none';const di=document.getElementById('diskinfo');if(di)di.style.display='none';const ni=document.getElementById('netinfo');if(ni)ni.style.display='none';load(true)});
|
||||
function fmtB(b){if(b==null)return '-';const u=['B','KB','MB','GB','TB'];let i=0;while(b>=1024&&i<4){b/=1024;i++}return b.toFixed(b>=100?0:1)+u[i]}
|
||||
function fmtR(b){return fmtB(b)+'/s'}
|
||||
function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<')}
|
||||
async function dropMem(){if(!confirm('一键回收未使用的内存(drop caches)?'))return;const r=await fetch('/api/mem/drop',{method:'POST'});const j=await r.json();alert(j.ok?('已回收,可用 '+fmtB(j.available)):('失败: '+j.error));load(true)}
|
||||
async function optimizeSys(){if(!confirm('一键优化系统:回收内存缓存 + 数据落盘 + 内存整理?'))return;const r=await fetch('/api/sys/optimize',{method:'POST'});const j=await r.json();alert(j.ok?(j.steps.join('\n')+'\n释放内存 '+fmtB(j.freed)+',当前可用 '+fmtB(j.available)):('失败'));load(true)}
|
||||
let lastAdvise=0;
|
||||
async function cpuAdvise(){const el=document.getElementById('cpuadvise');const now=Date.now();if(now-lastAdvise<15000){if(el.innerHTML)el.style.display='';return;}lastAdvise=now;try{const r=await fetch('/api/cpu/advise');const j=await r.json();const col=j.level==='轻松'||j.level==='正常'?'#20a53a':(j.level==='偏高'?'#ff9900':'#f00');document.getElementById('cpuadvise').style.display='';document.getElementById('cpuadvise').innerHTML=`<div class="model">📊 负载诊断:<b style="color:${col}">${j.level}</b> <span class="mut">(1分钟负载 ${j.load[0]} ÷ ${j.logical}核 = 单核 ${j.per_core})</span></div><div style="margin:4px 0">${esc(j.verdict)}</div><div class="mut">如何更流畅:</div><div>${j.tips.map(t=>`<div>· ${esc(t)}</div>`).join('')}</div><div style="margin-top:6px"><button class="bigbtn" onclick="optimizeSys()">🚀 一键优化系统</button></div>`}catch(e){}}
|
||||
async function kill(pid,name){if(!confirm('结束进程 '+name+' (PID '+pid+') ?'))return;const r=await fetch('/api/kill',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({pid})});const j=await r.json();alert(j.ok?'已发送结束信号':('失败: '+j.error));load(true)}
|
||||
function cmpVal(p,key){
|
||||
if(key==='name')return String(p.name||'');
|
||||
@@ -141,6 +147,7 @@ const I=j.info||{},P=j.percpu||[];
|
||||
document.getElementById('cpuinfo').style.display='';
|
||||
document.getElementById('cpuinfo').innerHTML=`<div class="model">${esc(I.model||'未知CPU')} <span class="mut">${esc(I.arch||'')}</span></div><div>物理 ${I.physical||'-'} 核 / 逻辑 ${I.logical||'-'} 核 · ${I.sockets?('共 '+I.sockets+' 路 × '+I.cores_per_socket+'核 × '+I.threads_per_core+'线程'):''} · 当前 ${I.freq_cur||'-'} MHz(${I.freq_min||'-'} ~ ${I.freq_max||'-'} MHz)</div><div class="mut">L1d ${esc(I.l1d||'-')} · L1i ${esc(I.l1i||'-')} · L2 ${esc(I.l2||'-')} · L3 ${esc(I.l3||'-')}</div><div class="corebar">${P.map((v,i)=>`<div class="corecell"><div class="cb"><i style="width:${Math.min(v,100)}%"></i></div><div class="ct">#${i} ${v}%</div></div>`).join('')}</div>`;
|
||||
document.getElementById('sub').textContent=`共 ${j.total} 个进程,显示占用最高的前 ${j.items.length} 个(进程CPU为单进程占用,单核满=100%,16核满=1600%,进程之和÷16≈整机占用)`;
|
||||
cpuAdvise();
|
||||
renderHeader();
|
||||
B.innerHTML=j.items.map(p=>`<tr><td><a class="proc">${esc(p.name)}</a></td><td class="num">${p.cpu}</td><td class="num">${p.threads}</td><td class="num">${p.pid}</td><td>${esc(p.desc)}</td><td>${esc(p.user)}</td><td class="${p.active?'st-run':'st-sleep'}">${p.status}</td><td><button class="kill" onclick="kill(${p.pid},'${esc(p.name)}')">结束</button></td></tr>`).join('');
|
||||
}else if(cur==='mem'){
|
||||
@@ -186,7 +193,12 @@ document.getElementById('netinfo').innerHTML=`<div class="model">出口 ${esc(NW
|
||||
}
|
||||
}catch(e){if(manual)alert('加载失败: '+e)}
|
||||
}
|
||||
load(true);setInterval(()=>load(false),3000);
|
||||
load(true);
|
||||
let ivalSec=3,timer=null,paused=false;
|
||||
function startTimer(){if(timer)clearInterval(timer);if(!paused)timer=setInterval(()=>load(false),ivalSec*1000)}
|
||||
function setIval(v){ivalSec=+v;startTimer()}
|
||||
function togglePause(){paused=!paused;document.getElementById('pauseBtn').textContent=paused?'▶ 恢复刷新':'⏸ 停止刷新';startTimer()}
|
||||
startTimer();
|
||||
</script>
|
||||
<footer style="position:fixed;bottom:0;left:0;right:0;text-align:center;padding:6px;font-size:12px;color:#999;background:#fff;border-top:1px solid #eee;z-index:999">
|
||||
<a href="https://nav.88531.cn" target="_blank" rel="noopener">资享导航站</a> ·
|
||||
|
||||
Reference in new issue
Block a user