"""系统调用监控器的 Flask Web 服务层。 提供两个页面与一个 JSON 接口: - 实时监控页:展示各系统调用的累计调用次数 - 配置页:增删需要追踪的系统调用名称 - /api/counts:返回当前计数,供前端轮询 """ import json import threading from pathlib import Path from flask import Flask, jsonify, redirect, render_template, request, url_for from collector.syscall_tracer import get_tracer # 项目根目录与配置文件路径 BASE_DIR = Path(__file__).resolve().parent.parent CONFIG_PATH = BASE_DIR / "config" / "monitors.json" # 配置读写锁:防止多个请求并发修改 monitors.json 时互相覆盖 _config_lock = threading.Lock() def _read_config() -> dict: """读取监控配置;文件不存在时返回空配置。""" if not CONFIG_PATH.exists(): return {"syscalls": []} with CONFIG_PATH.open("r", encoding="utf-8") as f: return json.load(f) def _write_config(data: dict) -> None: """原子化写入配置:先写临时文件再替换,避免写入过程中被读到半个文件。""" CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) tmp = CONFIG_PATH.with_suffix(".json.tmp") with tmp.open("w", encoding="utf-8") as f: # ensure_ascii=False 保留中文注释字段的原始字符 json.dump(data, f, indent=2, ensure_ascii=False) tmp.replace(CONFIG_PATH) def create_app() -> Flask: """Flask 应用工厂:创建并返回配置好的 Flask 实例。""" app = Flask(__name__, template_folder="templates", static_folder="static") # 初始化系统调用追踪器,由其负责挂载 eBPF 探针并维护计数 tracer = get_tracer(CONFIG_PATH) @app.get("/") def index(): # 实时监控首页:渲染当前监控项列表,前端会定时轮询 /api/counts 刷新数据 cfg = _read_config() return render_template("index.html", syscalls=cfg.get("syscalls", [])) @app.get("/api/counts") def api_counts(): # 返回各系统调用的累计调用次数(dict:syscall 名 -> 次数) return jsonify(tracer.get_counts()) @app.route("/config", methods=["GET", "POST"]) def config_page(): # 加锁保护配置文件的读-改-写流程,避免并发请求导致更新丢失 with _config_lock: cfg = _read_config() syscalls = list(cfg.get("syscalls", [])) if request.method == "POST": # 处理「添加」/「移除」表单:根据 action 字段决定操作 action = request.form.get("action", "") name = (request.form.get("name") or "").strip() if action == "add" and name and name not in syscalls: syscalls.append(name) elif action == "remove" and name in syscalls: syscalls.remove(name) cfg["syscalls"] = syscalls _write_config(cfg) # PRG 模式:提交后重定向到配置页,避免刷新重复提交 return redirect(url_for("config_page")) return render_template("config.html", syscalls=syscalls) return app