diff --git a/app.py b/app.py index 7176a40..1986dca 100644 --- a/app.py +++ b/app.py @@ -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 diff --git a/docs/cpu.png b/docs/cpu.png index dd16f16..6988a45 100644 Binary files a/docs/cpu.png and b/docs/cpu.png differ diff --git a/public/index.html b/public/index.html index eaa553f..c947157 100644 --- a/public/index.html +++ b/public/index.html @@ -56,7 +56,7 @@ button.refresh{background:#20a53a;color:#fff;border:none;border-radius:3px;paddi
磁盘
网络
-v1.3.2 +v1.3.3 资享导航站 @@ -65,6 +65,7 @@ button.refresh{background:#20a53a;color:#fff;border:none;border-radius:3px;paddi
+ @@ -78,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(/📊 负载诊断:${j.level} (1分钟负载 ${j.load[0]} ÷ ${j.logical}核 = 单核 ${j.per_core})
${esc(j.verdict)}
如何更流畅:
${j.tips.map(t=>`
· ${esc(t)}
`).join('')}
`}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||''); @@ -143,6 +147,7 @@ const I=j.info||{},P=j.percpu||[]; document.getElementById('cpuinfo').style.display=''; document.getElementById('cpuinfo').innerHTML=`
${esc(I.model||'未知CPU')} ${esc(I.arch||'')}
物理 ${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)
L1d ${esc(I.l1d||'-')} · L1i ${esc(I.l1i||'-')} · L2 ${esc(I.l2||'-')} · L3 ${esc(I.l3||'-')}
${P.map((v,i)=>`
#${i} ${v}%
`).join('')}
`; document.getElementById('sub').textContent=`共 ${j.total} 个进程,显示占用最高的前 ${j.items.length} 个(进程CPU为单进程占用,单核满=100%,16核满=1600%,进程之和÷16≈整机占用)`; +cpuAdvise(); renderHeader(); B.innerHTML=j.items.map(p=>`${esc(p.name)}${p.cpu}${p.threads}${p.pid}${esc(p.desc)}${esc(p.user)}${p.status}`).join(''); }else if(cur==='mem'){