-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwatchdog.py
More file actions
233 lines (201 loc) · 8.54 KB
/
Copy pathwatchdog.py
File metadata and controls
233 lines (201 loc) · 8.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# -*- encoding: utf-8 -*-
"""V1.02 监控看门狗 (watchdog)
- 每 30 秒检查 V1.02 监控进程 + ffmpeg 子进程
- 如果 V1.02 退出 → 弹 Windows 原生弹窗提醒(MB_TOPMOST)
- 如果 ffmpeg 全挂 → 弹窗提醒
- 如果 status 文件超过 120 秒没更新 → 弹窗(ffmpeg 可能僵死)
- 自动发现 V1.02 PID(通过命令行过滤)—— 不用硬编码
"""
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', line_buffering=True)
import os
import time
import ctypes
import subprocess
import threading
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
STATUS_FILE = os.path.join(BASE_DIR, "recording_status.txt")
DOWNLOADS_DIR = os.path.join(BASE_DIR, "downloads")
V1_02_MARKER = "main_V1.02.py"
CHECK_INTERVAL = 30 # 检查间隔(秒)
STALE_THRESHOLD = 120 # status 文件超过这么多秒没更新 → 警告
FFMPEG_MUST_HAVE = 1 # 至少要有 1 个 ffmpeg 在跑(如果 status 标记 RECORDING)
ALERTED = set() # 避免重复弹窗(每种异常只弹一次)
def find_v102_pid():
"""自动发现 V1.02 监控进程 PID(命令行含 main_V1.02.py)"""
try:
out = subprocess.run(
["powershell", "-NoProfile", "-Command",
"Get-CimInstance Win32_Process -Filter \"Name='python.exe'\" "
"| Where-Object { $_.CommandLine -like '*main_V1.02*' } "
"| Select-Object -ExpandProperty ProcessId"],
capture_output=True, text=True, timeout=10
)
for line in out.stdout.strip().splitlines():
line = line.strip()
if line.isdigit():
return int(line)
except Exception as e:
print(f"[warn] find_v102_pid: {e}")
return None
def is_pid_alive(pid):
"""检查 PID 是否还活着"""
if not pid:
return False
try:
kernel32 = ctypes.windll.kernel32
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
STILL_ACTIVE = 259
handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
if not handle:
return False
exit_code = ctypes.c_ulong()
kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code))
kernel32.CloseHandle(handle)
return exit_code.value == STILL_ACTIVE
except Exception:
return False
def count_ffmpeg():
"""统计当前在跑的 ffmpeg 进程数"""
try:
out = subprocess.run(
["tasklist", "/FI", "IMAGENAME eq ffmpeg.exe"],
capture_output=True, text=True, timeout=10
)
return out.stdout.lower().count("ffmpeg.exe")
except Exception:
return -1
def get_status_age():
"""返回 status 文件最后修改到现在过了多少秒;不存在返回 -1"""
if not os.path.exists(STATUS_FILE):
return -1
return int(time.time() - os.path.getmtime(STATUS_FILE))
def is_recording_status():
"""status 文件内容是否标记 RECORDING"""
if not os.path.exists(STATUS_FILE):
return False
try:
with open(STATUS_FILE, "r", encoding="utf-8") as f:
return f.read().strip().startswith("RECORDING|")
except Exception:
return False
def alert_popup(title, message, level="warning"):
"""弹 Windows 原生弹窗(MB_TOPMOST 置顶)"""
icon = 0x30 # MB_ICONWARNING
if level == "error":
icon = 0x10 # MB_ICONERROR
elif level == "info":
icon = 0x40 # MB_ICONINFORMATION
# 0x1000 = MB_TOPMOST
def _show():
try:
ctypes.windll.user32.MessageBoxW(0, message, title, icon | 0x1000)
except Exception as e:
print(f"[warn] MessageBox failed: {e}")
threading.Thread(target=_show, daemon=True).start()
print(f"\n{'='*60}")
print(f" ⚠️ {title}")
print(f" {message}")
print(f"{'='*60}\n")
def fmt_duration(sec):
sec = max(0, int(sec))
h, m, s = sec // 3600, (sec % 3600) // 60, sec % 60
if h:
return f"{h}h {m}m {s}s"
return f"{m}m {s}s"
def main():
print("=" * 60)
print(" V1.02 Watchdog - 监控看门狗")
print("=" * 60)
print(f" Project: {BASE_DIR}")
print(f" Status file: {STATUS_FILE}")
print(f" Check interval: {CHECK_INTERVAL}s")
print(f" Stale threshold: {STALE_THRESHOLD}s")
print(f" Marker: {V1_02_MARKER}")
print("=" * 60)
print(" 按 Ctrl+C 停止 watchdog")
print()
v1_pid = None
last_pid_check = 0
try:
while True:
now = time.time()
ts_short = time.strftime("%H:%M:%S")
# 每 60 秒重新发现 V1.02 PID(V1.02 重启后 PID 会变)
if now - last_pid_check > 60 or v1_pid is None:
v1_pid = find_v102_pid()
last_pid_check = now
if v1_pid:
print(f"[{ts_short}] 发现 V1.02 监控 PID = {v1_pid}")
else:
print(f"[{ts_short}] ⚠️ 找不到 V1.02 监控进程")
v1_alive = is_pid_alive(v1_pid) if v1_pid else False
ffmpeg_n = count_ffmpeg()
status_age = get_status_age()
recording_now = is_recording_status()
print(f"[{ts_short}] V1.02={v1_alive}(PID {v1_pid}) ffmpeg={ffmpeg_n} "
f"status_age={status_age}s recording={recording_now}")
# 检查 1: V1.02 监控进程退出
if v1_pid and not v1_alive:
key = f"v102_dead_{v1_pid}"
if key not in ALERTED:
ALERTED.add(key)
alert_popup(
"[V1.02] ⚠️ 监控进程已退出",
f"V1.02 监控进程 (PID {v1_pid}) 已退出!\n\n"
f"录制已中断。\n\n"
f"处理建议:\n"
f"1. 检查 {STATUS_FILE} 看最后状态\n"
f"2. 重新双击 start.bat 启动录制服务\n"
f"3. 检查录制文件是否完整 (downloads\\)",
level="error"
)
# 检查 2: status 标记 RECORDING 但 ffmpeg 全挂
if recording_now and ffmpeg_n == 0:
key = f"ffmpeg_dead_{status_age}"
if key not in ALERTED:
ALERTED.add(key)
alert_popup(
"[V1.02] ⚠️ 录制异常中断",
f"状态文件显示 REC 中,但无 ffmpeg 进程!\n\n"
f"可能 ffmpeg crash 或被误杀。\n\n"
f"处理建议:\n"
f"1. 查看 V1.02 监控窗口日志\n"
f"2. 检查 downloads\\ 是否有 .aac 在写入\n"
f"3. 必要时重启 start.bat",
level="error"
)
# 检查 3: status 文件超过 STALE_THRESHOLD 秒没更新(且 V1.02 活着)
if v1_alive and status_age > STALE_THRESHOLD and status_age > 0:
key = f"stale_{int(status_age / 60)}"
if key not in ALERTED:
ALERTED.add(key)
alert_popup(
"[V1.02] ⚠️ 状态文件过期",
f"recording_status.txt 已经 {fmt_duration(status_age)} 没更新了!\n\n"
f"V1.02 监控还活着但可能卡住。\n\n"
f"处理建议:\n"
f"1. 看 V1.02 监控窗口是否有响应\n"
f"2. 必要时手动 Ctrl+C 停止 V1.02 然后重启",
level="warning"
)
# 检查 4: 完全没找到 V1.02 进程(持续 2 分钟)
if v1_pid is None and now - last_pid_check > 120:
key = f"v102_missing_{int(now / 120)}"
if key not in ALERTED:
ALERTED.add(key)
alert_popup(
"[V1.02] ❌ 监控未启动",
f"超过 2 分钟没找到 V1.02 监控进程!\n\n"
f"可能 start.bat 没运行,或 V1.02 启动失败。\n\n"
f"处理建议:\n"
f"1. 打开任务管理器看 python.exe 进程\n"
f"2. 双击 start.bat 启动 V1.02",
level="error"
)
time.sleep(CHECK_INTERVAL)
except KeyboardInterrupt:
print("\n[watchdog] 收到 Ctrl+C,停止")
return
if __name__ == "__main__":
main()