refactor: optimize structure, stability and runtime performance
This commit is contained in:
226
routes/admin_api/infra_api.py
Normal file
226
routes/admin_api/infra_api.py
Normal file
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import database
|
||||
from app_logger import get_logger
|
||||
from flask import jsonify, session
|
||||
from routes.admin_api import admin_api_bp
|
||||
from routes.decorators import admin_required
|
||||
from services.time_utils import BEIJING_TZ, get_beijing_now
|
||||
|
||||
logger = get_logger("app")
|
||||
|
||||
|
||||
@admin_api_bp.route("/stats", methods=["GET"])
|
||||
@admin_required
|
||||
def get_system_stats():
|
||||
"""获取系统统计"""
|
||||
stats = database.get_system_stats()
|
||||
stats["admin_username"] = session.get("admin_username", "admin")
|
||||
return jsonify(stats)
|
||||
|
||||
|
||||
@admin_api_bp.route("/browser_pool/stats", methods=["GET"])
|
||||
@admin_required
|
||||
def get_browser_pool_stats():
|
||||
"""获取截图线程池状态"""
|
||||
try:
|
||||
from browser_pool_worker import get_browser_worker_pool
|
||||
|
||||
pool = get_browser_worker_pool()
|
||||
stats = pool.get_stats() or {}
|
||||
|
||||
worker_details = []
|
||||
for w in stats.get("workers") or []:
|
||||
last_ts = float(w.get("last_active_ts") or 0)
|
||||
last_active_at = None
|
||||
if last_ts > 0:
|
||||
try:
|
||||
last_active_at = datetime.fromtimestamp(last_ts, tz=BEIJING_TZ).strftime("%Y-%m-%d %H:%M:%S")
|
||||
except Exception:
|
||||
last_active_at = None
|
||||
|
||||
created_ts = w.get("browser_created_at")
|
||||
created_at = None
|
||||
if created_ts:
|
||||
try:
|
||||
created_at = datetime.fromtimestamp(float(created_ts), tz=BEIJING_TZ).strftime("%Y-%m-%d %H:%M:%S")
|
||||
except Exception:
|
||||
created_at = None
|
||||
|
||||
worker_details.append(
|
||||
{
|
||||
"worker_id": w.get("worker_id"),
|
||||
"idle": bool(w.get("idle")),
|
||||
"has_browser": bool(w.get("has_browser")),
|
||||
"total_tasks": int(w.get("total_tasks") or 0),
|
||||
"failed_tasks": int(w.get("failed_tasks") or 0),
|
||||
"browser_use_count": int(w.get("browser_use_count") or 0),
|
||||
"browser_created_at": created_at,
|
||||
"browser_created_ts": created_ts,
|
||||
"last_active_at": last_active_at,
|
||||
"last_active_ts": last_ts,
|
||||
"thread_alive": bool(w.get("thread_alive")),
|
||||
}
|
||||
)
|
||||
|
||||
total_workers = len(worker_details) if worker_details else int(stats.get("pool_size") or 0)
|
||||
return jsonify(
|
||||
{
|
||||
"total_workers": total_workers,
|
||||
"active_workers": int(stats.get("busy_workers") or 0),
|
||||
"idle_workers": int(stats.get("idle_workers") or 0),
|
||||
"queue_size": int(stats.get("queue_size") or 0),
|
||||
"workers": worker_details,
|
||||
"summary": {
|
||||
"total_tasks": int(stats.get("total_tasks") or 0),
|
||||
"failed_tasks": int(stats.get("failed_tasks") or 0),
|
||||
"success_rate": stats.get("success_rate"),
|
||||
},
|
||||
"server_time_cst": get_beijing_now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception(f"[AdminAPI] 获取截图线程池状态失败: {e}")
|
||||
return jsonify({"error": "获取截图线程池状态失败"}), 500
|
||||
|
||||
|
||||
@admin_api_bp.route("/docker_stats", methods=["GET"])
|
||||
@admin_required
|
||||
def get_docker_stats():
|
||||
"""获取Docker容器运行状态"""
|
||||
import subprocess
|
||||
|
||||
docker_status = {
|
||||
"running": False,
|
||||
"container_name": "N/A",
|
||||
"uptime": "N/A",
|
||||
"memory_usage": "N/A",
|
||||
"memory_limit": "N/A",
|
||||
"memory_percent": "N/A",
|
||||
"cpu_percent": "N/A",
|
||||
"status": "Unknown",
|
||||
}
|
||||
|
||||
try:
|
||||
if os.path.exists("/.dockerenv"):
|
||||
docker_status["running"] = True
|
||||
|
||||
try:
|
||||
with open("/etc/hostname", "r") as f:
|
||||
docker_status["container_name"] = f.read().strip()
|
||||
except Exception as e:
|
||||
logger.debug(f"读取容器名称失败: {e}")
|
||||
|
||||
try:
|
||||
if os.path.exists("/sys/fs/cgroup/memory.current"):
|
||||
with open("/sys/fs/cgroup/memory.current", "r") as f:
|
||||
mem_total = int(f.read().strip())
|
||||
|
||||
cache = 0
|
||||
if os.path.exists("/sys/fs/cgroup/memory.stat"):
|
||||
with open("/sys/fs/cgroup/memory.stat", "r") as f:
|
||||
for line in f:
|
||||
if line.startswith("inactive_file "):
|
||||
cache = int(line.split()[1])
|
||||
break
|
||||
|
||||
mem_bytes = mem_total - cache
|
||||
docker_status["memory_usage"] = "{:.2f} MB".format(mem_bytes / 1024 / 1024)
|
||||
|
||||
if os.path.exists("/sys/fs/cgroup/memory.max"):
|
||||
with open("/sys/fs/cgroup/memory.max", "r") as f:
|
||||
limit_str = f.read().strip()
|
||||
if limit_str != "max":
|
||||
limit_bytes = int(limit_str)
|
||||
docker_status["memory_limit"] = "{:.2f} GB".format(limit_bytes / 1024 / 1024 / 1024)
|
||||
docker_status["memory_percent"] = "{:.2f}%".format(mem_bytes / limit_bytes * 100)
|
||||
elif os.path.exists("/sys/fs/cgroup/memory/memory.usage_in_bytes"):
|
||||
with open("/sys/fs/cgroup/memory/memory.usage_in_bytes", "r") as f:
|
||||
mem_bytes = int(f.read().strip())
|
||||
docker_status["memory_usage"] = "{:.2f} MB".format(mem_bytes / 1024 / 1024)
|
||||
|
||||
with open("/sys/fs/cgroup/memory/memory.limit_in_bytes", "r") as f:
|
||||
limit_bytes = int(f.read().strip())
|
||||
if limit_bytes < 1e18:
|
||||
docker_status["memory_limit"] = "{:.2f} GB".format(limit_bytes / 1024 / 1024 / 1024)
|
||||
docker_status["memory_percent"] = "{:.2f}%".format(mem_bytes / limit_bytes * 100)
|
||||
except Exception as e:
|
||||
logger.debug(f"读取内存信息失败: {e}")
|
||||
|
||||
try:
|
||||
if os.path.exists("/sys/fs/cgroup/cpu.stat"):
|
||||
cpu_usage = 0
|
||||
with open("/sys/fs/cgroup/cpu.stat", "r") as f:
|
||||
for line in f:
|
||||
if line.startswith("usage_usec"):
|
||||
cpu_usage = int(line.split()[1])
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
cpu_usage2 = 0
|
||||
with open("/sys/fs/cgroup/cpu.stat", "r") as f:
|
||||
for line in f:
|
||||
if line.startswith("usage_usec"):
|
||||
cpu_usage2 = int(line.split()[1])
|
||||
break
|
||||
|
||||
cpu_percent = (cpu_usage2 - cpu_usage) / 0.1 / 1e6 * 100
|
||||
docker_status["cpu_percent"] = "{:.2f}%".format(cpu_percent)
|
||||
elif os.path.exists("/sys/fs/cgroup/cpu/cpuacct.usage"):
|
||||
with open("/sys/fs/cgroup/cpu/cpuacct.usage", "r") as f:
|
||||
cpu_usage = int(f.read().strip())
|
||||
|
||||
time.sleep(0.1)
|
||||
with open("/sys/fs/cgroup/cpu/cpuacct.usage", "r") as f:
|
||||
cpu_usage2 = int(f.read().strip())
|
||||
|
||||
cpu_percent = (cpu_usage2 - cpu_usage) / 0.1 / 1e9 * 100
|
||||
docker_status["cpu_percent"] = "{:.2f}%".format(cpu_percent)
|
||||
except Exception as e:
|
||||
logger.debug(f"读取CPU信息失败: {e}")
|
||||
|
||||
try:
|
||||
# 读取系统运行时间
|
||||
with open('/proc/uptime', 'r') as f:
|
||||
system_uptime = float(f.read().split()[0])
|
||||
|
||||
# 读取 PID 1 的启动时间 (jiffies)
|
||||
with open('/proc/1/stat', 'r') as f:
|
||||
stat = f.read().split()
|
||||
starttime_jiffies = int(stat[21])
|
||||
|
||||
# 获取 CLK_TCK (通常是 100)
|
||||
clk_tck = os.sysconf(os.sysconf_names['SC_CLK_TCK'])
|
||||
|
||||
# 计算容器运行时长(秒)
|
||||
container_uptime_seconds = system_uptime - (starttime_jiffies / clk_tck)
|
||||
|
||||
# 格式化为可读字符串
|
||||
days = int(container_uptime_seconds // 86400)
|
||||
hours = int((container_uptime_seconds % 86400) // 3600)
|
||||
minutes = int((container_uptime_seconds % 3600) // 60)
|
||||
|
||||
if days > 0:
|
||||
docker_status["uptime"] = f"{days}天{hours}小时{minutes}分钟"
|
||||
elif hours > 0:
|
||||
docker_status["uptime"] = f"{hours}小时{minutes}分钟"
|
||||
else:
|
||||
docker_status["uptime"] = f"{minutes}分钟"
|
||||
except Exception as e:
|
||||
logger.debug(f"获取容器运行时间失败: {e}")
|
||||
|
||||
docker_status["status"] = "Running"
|
||||
|
||||
else:
|
||||
docker_status["status"] = "Not in Docker"
|
||||
except Exception as e:
|
||||
docker_status["status"] = f"Error: {str(e)}"
|
||||
|
||||
return jsonify(docker_status)
|
||||
|
||||
Reference in New Issue
Block a user