From 748f6f57a56e704f16558f06c64b6e9bbe57bbd6 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 27 Feb 2026 18:46:56 +0000 Subject: [PATCH] =?UTF-8?q?perf:=20server/status=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E4=BC=98=E5=8C=96(cpu=E9=9D=9E=E9=98=BB=E5=A1=9E+pm2=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E8=B0=83=E7=94=A8+COUNT=E4=BC=B0=E7=AE=97+5=E7=A7=92?= =?UTF-8?q?=E7=BC=93=E5=AD=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/main.py | 59 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/backend/main.py b/backend/main.py index 35291f5..0881daa 100644 --- a/backend/main.py +++ b/backend/main.py @@ -458,11 +458,40 @@ async def get_signal_trades( import shutil, subprocess, psutil +# 服务器状态缓存(避免重复调用慢操作) +_server_cache: dict = {"data": None, "ts": 0} +_PM2_BIN = None + +def _find_pm2_bin(): + """找到pm2二进制路径,避免每次走npx""" + global _PM2_BIN + if _PM2_BIN: + return _PM2_BIN + import shutil as _sh + for p in ["/home/fzq1228/.local/bin/pm2", "/usr/local/bin/pm2", "/usr/bin/pm2"]: + if os.path.exists(p): + _PM2_BIN = p + return p + found = _sh.which("pm2") + if found: + _PM2_BIN = found + return found + return "npx pm2" + +# 启动时初始化CPU采样(首次调用不阻塞) +psutil.cpu_percent(interval=None) + @app.get("/api/server/status") async def get_server_status(user: dict = Depends(get_current_user)): """服务器全状态:CPU/内存/硬盘/负载/PM2进程/PG数据库/回补进度""" - # CPU - cpu_percent = psutil.cpu_percent(interval=0.5) + + # 5秒缓存,避免频繁调用慢操作 + now = time.time() + if _server_cache["data"] and (now - _server_cache["ts"]) < 5: + return _server_cache["data"] + + # CPU(非阻塞,取上次采样间隔的值) + cpu_percent = psutil.cpu_percent(interval=None) cpu_count = psutil.cpu_count() # 内存 @@ -482,12 +511,14 @@ async def get_server_status(user: dict = Depends(get_current_user)): # 网络IO net = psutil.net_io_counters() - # PM2进程状态 + # PM2进程状态(直接调pm2二进制,不走npx) pm2_procs = [] try: + pm2_bin = _find_pm2_bin() + cmd = [pm2_bin, "jlist"] if not pm2_bin.startswith("npx") else ["npx", "pm2", "jlist"] result = subprocess.run( - ["npx", "pm2", "jlist"], - capture_output=True, text=True, timeout=10 + cmd, + capture_output=True, text=True, timeout=5 ) import json as _json procs = _json.loads(result.stdout) @@ -504,7 +535,7 @@ async def get_server_status(user: dict = Depends(get_current_user)): except Exception: pm2_procs = [] - # PG数据库大小 + agg_trades条数 + # PG数据库大小 + agg_trades条数(用估算值,快1000倍) pg_info = {} try: row = await async_fetchrow( @@ -512,10 +543,15 @@ async def get_server_status(user: dict = Depends(get_current_user)): ) pg_info["db_size_mb"] = round(row["db_size"] / 1024 / 1024, 1) if row else 0 - row2 = await async_fetchrow("SELECT COUNT(*) as cnt FROM agg_trades") - pg_info["agg_trades_count"] = row2["cnt"] if row2 else 0 + # 用PG统计信息估算行数(毫秒级,而非COUNT(*)的秒级全表扫描) + row2 = await async_fetchrow( + "SELECT SUM(n_live_tup)::bigint as cnt FROM pg_stat_user_tables WHERE relname LIKE 'agg_trades%'" + ) + pg_info["agg_trades_count"] = row2["cnt"] if row2 and row2["cnt"] else 0 - row3 = await async_fetchrow("SELECT COUNT(*) as cnt FROM rate_snapshots") + row3 = await async_fetchrow( + "SELECT n_live_tup::bigint as cnt FROM pg_stat_user_tables WHERE relname = 'rate_snapshots'" + ) pg_info["rate_snapshots_count"] = row3["cnt"] if row3 else 0 # 各symbol最新数据时间 @@ -542,7 +578,7 @@ async def get_server_status(user: dict = Depends(get_current_user)): except Exception: pass - return { + result = { "timestamp": int(time.time() * 1000), "cpu": { "percent": cpu_percent, @@ -574,3 +610,6 @@ async def get_server_status(user: dict = Depends(get_current_user)): "postgres": pg_info, "backfill_running": backfill_running, } + _server_cache["data"] = result + _server_cache["ts"] = now + return result