Files
syscall_monitor/web/app.py
MarceloZoeng 26a5f99587
All checks were successful
CI / lint-and-build (push) Successful in 8s
中文+README
2026-06-14 12:08:27 +08:00

85 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""系统调用监控器的 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():
# 返回各系统调用的累计调用次数dictsyscall 名 -> 次数)
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