Files
zsglpt/routes/admin_api/infra_api.py

249 lines
10 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import os
import socket
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 worker in stats.get("workers") or []:
last_ts = float(worker.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 = worker.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": worker.get("worker_id"),
"idle": bool(worker.get("idle")),
"has_browser": bool(worker.get("has_browser")),
"total_tasks": int(worker.get("total_tasks") or 0),
"failed_tasks": int(worker.get("failed_tasks") or 0),
"browser_use_count": int(worker.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(worker.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
def _format_duration(seconds: int) -> str:
total = max(0, int(seconds or 0))
days = total // 86400
hours = (total % 86400) // 3600
minutes = (total % 3600) // 60
if days > 0:
return f"{days}{hours}小时{minutes}分钟"
if hours > 0:
return f"{hours}小时{minutes}分钟"
return f"{minutes}分钟"
def _fill_host_service_stats(docker_status: dict) -> None:
import psutil
process = psutil.Process(os.getpid())
memory_info = process.memory_info()
virtual_memory = psutil.virtual_memory()
rss_bytes = float(memory_info.rss or 0)
total_bytes = float(virtual_memory.total or 0)
memory_percent = (rss_bytes / total_bytes * 100.0) if total_bytes > 0 else 0.0
docker_status.update(
{
"running": True,
"status": "Host Service",
"container_name": f"host:{socket.gethostname()}",
"uptime": _format_duration(int(time.time() - float(process.create_time() or time.time()))),
"memory_usage": f"{rss_bytes / 1024 / 1024:.2f} MB",
"memory_limit": f"{total_bytes / 1024 / 1024 / 1024:.2f} GB" if total_bytes > 0 else "N/A",
"memory_percent": f"{memory_percent:.2f}%",
"cpu_percent": f"{max(0.0, float(process.cpu_percent(interval=0.1))):.2f}%",
}
)
@admin_api_bp.route("/docker_stats", methods=["GET"])
@admin_required
def get_docker_stats():
"""获取容器运行状态(非容器部署时返回当前服务进程状态)"""
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", encoding="utf-8") as f:
docker_status["container_name"] = f.read().strip() or "N/A"
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", encoding="utf-8") 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", encoding="utf-8") as f:
for line in f:
if line.startswith("inactive_file "):
cache = int(line.split()[1])
break
mem_bytes = max(0, mem_total - cache)
docker_status["memory_usage"] = f"{mem_bytes / 1024 / 1024:.2f} MB"
if os.path.exists("/sys/fs/cgroup/memory.max"):
with open("/sys/fs/cgroup/memory.max", "r", encoding="utf-8") as f:
limit_str = f.read().strip()
if limit_str != "max":
limit_bytes = int(limit_str)
if limit_bytes > 0:
docker_status["memory_limit"] = f"{limit_bytes / 1024 / 1024 / 1024:.2f} GB"
docker_status["memory_percent"] = f"{mem_bytes / limit_bytes * 100:.2f}%"
elif os.path.exists("/sys/fs/cgroup/memory/memory.usage_in_bytes"):
with open("/sys/fs/cgroup/memory/memory.usage_in_bytes", "r", encoding="utf-8") as f:
mem_bytes = int(f.read().strip())
docker_status["memory_usage"] = f"{mem_bytes / 1024 / 1024:.2f} MB"
with open("/sys/fs/cgroup/memory/memory.limit_in_bytes", "r", encoding="utf-8") as f:
limit_bytes = int(f.read().strip())
if 0 < limit_bytes < 1e18:
docker_status["memory_limit"] = f"{limit_bytes / 1024 / 1024 / 1024:.2f} GB"
docker_status["memory_percent"] = f"{mem_bytes / limit_bytes * 100:.2f}%"
except Exception as e:
logger.debug(f"读取容器内存信息失败: {e}")
try:
if os.path.exists("/sys/fs/cgroup/cpu.stat"):
usage1 = 0
with open("/sys/fs/cgroup/cpu.stat", "r", encoding="utf-8") as f:
for line in f:
if line.startswith("usage_usec"):
usage1 = int(line.split()[1])
break
time.sleep(0.1)
usage2 = 0
with open("/sys/fs/cgroup/cpu.stat", "r", encoding="utf-8") as f:
for line in f:
if line.startswith("usage_usec"):
usage2 = int(line.split()[1])
break
cpu_percent = (usage2 - usage1) / 0.1 / 1e6 * 100
docker_status["cpu_percent"] = f"{max(0.0, cpu_percent):.2f}%"
elif os.path.exists("/sys/fs/cgroup/cpu/cpuacct.usage"):
with open("/sys/fs/cgroup/cpu/cpuacct.usage", "r", encoding="utf-8") as f:
usage1 = int(f.read().strip())
time.sleep(0.1)
with open("/sys/fs/cgroup/cpu/cpuacct.usage", "r", encoding="utf-8") as f:
usage2 = int(f.read().strip())
cpu_percent = (usage2 - usage1) / 0.1 / 1e9 * 100
docker_status["cpu_percent"] = f"{max(0.0, cpu_percent):.2f}%"
except Exception as e:
logger.debug(f"读取容器CPU信息失败: {e}")
try:
with open("/proc/uptime", "r", encoding="utf-8") as f:
system_uptime = float(f.read().split()[0])
with open("/proc/1/stat", "r", encoding="utf-8") as f:
stat = f.read().split()
starttime_jiffies = int(stat[21])
clk_tck = os.sysconf(os.sysconf_names["SC_CLK_TCK"])
uptime_seconds = int(system_uptime - (starttime_jiffies / clk_tck))
docker_status["uptime"] = _format_duration(uptime_seconds)
except Exception as e:
logger.debug(f"获取容器运行时间失败: {e}")
docker_status["status"] = "Running"
else:
_fill_host_service_stats(docker_status)
except Exception as e:
logger.exception(f"获取容器/服务状态失败: {e}")
docker_status["status"] = f"Error: {e}"
return jsonify(docker_status)