Files
syscall_monitor/web/app.py

85 lines
3.1 KiB
Python
Raw Normal View History

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