#!/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)