initial: blog-app snapshot
This commit is contained in:
96
services/watchdog.py
Normal file
96
services/watchdog.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""Watchdog service: report loading, process checks, trigger bookkeeping.
|
||||
|
||||
Mostly pure (no Flask imports). `trigger_watchdog_run` uses subprocess only;
|
||||
the caller (route) is responsible for flash/redirect.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import config
|
||||
from repositories import watchdog_repo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 简单防重: 模块级变量记录上次触发时间戳, cooldown 秒内重复 POST 返回失败
|
||||
_last_trigger_ts = 0.0
|
||||
|
||||
|
||||
def parse_days(raw, default=7):
|
||||
"""Clamp days query param to 1-90; non-numeric falls back to default."""
|
||||
try:
|
||||
value = int(raw)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
return max(1, min(90, value))
|
||||
|
||||
|
||||
def load_watchdog_reports(watchdog_conn, days=7):
|
||||
"""Return recent watchdog reports; [] when db/table missing or unreadable."""
|
||||
if not os.path.exists(config.WATCHDOG_DB):
|
||||
return []
|
||||
try:
|
||||
return watchdog_repo.list_reports(watchdog_conn, days=days)
|
||||
except sqlite3.Error:
|
||||
logger.exception("failed to read watchdog db")
|
||||
return []
|
||||
|
||||
|
||||
def is_watchdog_running():
|
||||
"""True if a python process running daily_watchdog.py already exists."""
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["pgrep", "-f", "daily_watchdog.py"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
).stdout
|
||||
except Exception:
|
||||
logger.exception("watchdog pgrep failed")
|
||||
return False
|
||||
for pid in out.split():
|
||||
try:
|
||||
with open(f"/proc/{pid}/cmdline", "rb") as f:
|
||||
argv0 = f.read().split(b"\x00")[0].decode(errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
if os.path.basename(argv0).startswith("python"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def trigger_watchdog_run(cooldown=None):
|
||||
"""Attempt to launch scripts/daily_watchdog.py in the background.
|
||||
|
||||
Returns (ok, status, message):
|
||||
- (True, "started", msg) launched successfully
|
||||
- (False, "cooldown", msg) hit the anti-repeat cooldown
|
||||
- (False, "running", msg) an instance is already running
|
||||
- (False, "error", msg) subprocess launch failed
|
||||
"""
|
||||
global _last_trigger_ts
|
||||
cooldown = config.WATCHDOG_RUN_COOLDOWN if cooldown is None else cooldown
|
||||
|
||||
now = time.time()
|
||||
if now - _last_trigger_ts < cooldown:
|
||||
return False, "cooldown", "操作太频繁, 请稍后再试"
|
||||
_last_trigger_ts = now
|
||||
|
||||
if is_watchdog_running():
|
||||
return False, "running", "daily_watchdog.py 已在运行中, 本次触发被忽略"
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.dirname(config.WATCHDOG_RUN_LOG), exist_ok=True)
|
||||
log_f = open(config.WATCHDOG_RUN_LOG, "a", encoding="utf-8")
|
||||
env = dict(os.environ)
|
||||
env["HOME"] = "/home/yi"
|
||||
subprocess.Popen(
|
||||
["/usr/bin/python3", config.WATCHDOG_SCRIPT],
|
||||
stdout=log_f, stderr=subprocess.STDOUT,
|
||||
cwd=config.BASE_DIR, env=env, start_new_session=True,
|
||||
)
|
||||
return True, "started", "已触发, 约 1-2 分钟后刷新页面查看新报告"
|
||||
except Exception as exc:
|
||||
logger.exception("failed to launch daily_watchdog.py")
|
||||
return False, "error", f"触发失败: {exc}"
|
||||
Reference in New Issue
Block a user