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