新增转图运行时健康监控与手动预热
变更项:\n1. 在 markdown_to_image 增加 get_md2img_health_snapshot 健康快照能力,输出 runtime 线程、事件循环、浏览器连接、启动来源与 PID 状态。\n2. 新增系统接口 GET /api/system/md2img_health,支持后台查询转图运行时健康信息。\n3. 新增系统接口 POST /api/system/md2img_warmup,支持后台手动触发转图预热并返回最新状态。\n4. 在资源监控页面接入转图健康状态条,展示运行时在线状态、浏览器连接状态及关键摘要信息。\n5. 在资源监控页面增加转图预热与状态刷新按钮,便于线上快速自愈与排障。\n6. 补充中文注释与错误提示,保持后端与前端可观测性一致。
This commit is contained in:
@@ -686,6 +686,73 @@ def _get_md2img_runtime() -> _Md2ImgRuntime:
|
||||
return _MD2IMG_RUNTIME
|
||||
|
||||
|
||||
def get_md2img_health_snapshot(ensure_runtime: bool = False) -> dict:
|
||||
"""获取 Markdown 转图运行时健康快照(同步)。
|
||||
|
||||
Args:
|
||||
ensure_runtime: 是否在采集前确保运行时已启动。
|
||||
- False: 仅观察当前状态,不主动拉起线程;
|
||||
- True: 先启动 md2img runtime,再返回状态,适合后台手动“刷新并拉起”场景。
|
||||
|
||||
Returns:
|
||||
dict: 结构化健康信息,便于后台页面直接展示。
|
||||
"""
|
||||
runtime = _get_md2img_runtime()
|
||||
if ensure_runtime:
|
||||
# 显式拉起运行时,方便后台做一次“冷启动检查”。
|
||||
runtime.ensure_started()
|
||||
|
||||
thread_obj = getattr(runtime, "_thread", None)
|
||||
loop_obj = getattr(runtime, "_loop", None)
|
||||
|
||||
runtime_started = bool(thread_obj is not None)
|
||||
runtime_thread_alive = bool(thread_obj.is_alive()) if thread_obj else False
|
||||
runtime_loop_running = bool(loop_obj.is_running()) if loop_obj else False
|
||||
runtime_loop_id = id(loop_obj) if loop_obj else None
|
||||
runtime_thread_name = thread_obj.name if thread_obj else ""
|
||||
|
||||
browser_manager = _BROWSER_MANAGER
|
||||
browser_connected = False
|
||||
browser_loop_owner = None
|
||||
browser_launch_source = ""
|
||||
browser_pid = None
|
||||
browser_proc_alive = None
|
||||
browser_error = ""
|
||||
if browser_manager is not None:
|
||||
try:
|
||||
browser_obj = getattr(browser_manager, "_browser", None)
|
||||
browser_connected = bool(browser_obj and browser_obj.is_connected())
|
||||
browser_loop_owner = getattr(browser_manager, "_owner_loop_id", None)
|
||||
browser_launch_source = str(getattr(browser_manager, "_last_launch_source", "") or "")
|
||||
browser_pid = getattr(getattr(browser_obj, "process", None), "pid", None) if browser_obj else None
|
||||
if browser_pid:
|
||||
# 通过 psutil 二次确认进程是否仍在,避免只看到历史 PID。
|
||||
browser_proc_alive = psutil.pid_exists(int(browser_pid))
|
||||
else:
|
||||
browser_proc_alive = None
|
||||
except Exception as e:
|
||||
browser_error = str(e)
|
||||
|
||||
return {
|
||||
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||
"runtime": {
|
||||
"started": runtime_started,
|
||||
"thread_alive": runtime_thread_alive,
|
||||
"thread_name": runtime_thread_name,
|
||||
"loop_running": runtime_loop_running,
|
||||
"loop_id": runtime_loop_id,
|
||||
},
|
||||
"browser": {
|
||||
"connected": browser_connected,
|
||||
"owner_loop_id": browser_loop_owner,
|
||||
"launch_source": browser_launch_source,
|
||||
"pid": browser_pid,
|
||||
"pid_alive": browser_proc_alive,
|
||||
"error": browser_error,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def _run_in_md2img_runtime(coro, timeout_seconds: Optional[int] = None):
|
||||
"""在 md2img 专用事件循环中执行协程,并在当前调用方 loop 中异步等待结果。"""
|
||||
runtime = _get_md2img_runtime()
|
||||
|
||||
Reference in New Issue
Block a user