diff --git a/_build/_cmsg_helpers.txt b/_build/_cmsg_helpers.txt new file mode 100644 index 0000000..30a302b --- /dev/null +++ b/_build/_cmsg_helpers.txt @@ -0,0 +1,18 @@ +加两个运维辅助脚本 + + _build/watch_stability.py 连续打公网统计可用率(验证隧道加固效果) + _build/desktop_state.py 列出当前可见窗口 + 输入桌面名 + (排查"桌面消失/一片蓝"这类问题用) + +这两个是查 2.1.1 / 2.1.2 那两个问题时写的,以后还会用到。 + +顺便记一笔当时的排查结论(2026-10-08): +用户报"桌面消失、一片蓝、有个蓝色窗口",查下来: + * 输入桌面是 Default(正常),explorer 在跑 + * 那 9 个"全屏窗口"全是 Windows 自己的 UWP 组件 + (开始菜单 / 搜索 / 锁屏 / 输入法),程序枚举时会误报全屏 + * 真正开着的是「设置」App 和任务管理器 + * 自启项里没有 ykxmz,也没有残留的隐藏桌面或测试进程 + * 18:42:41 有一次**非正常关机**(Kernel-Power 41 / EventLog 6008), + 19:58 重启。没有任何蓝屏或硬件错误记录 —— + 最符合"掉电或硬挂",不是软件崩溃。 diff --git a/_build/desktop_state.py b/_build/desktop_state.py new file mode 100644 index 0000000..de992df --- /dev/null +++ b/_build/desktop_state.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +r'''看当前桌面/窗口的真实状态 —— 诊断"桌面消失、一片蓝"。 + +用户报:"我现在桌面消失了,电脑一片蓝,然后还有个蓝色的窗口"。 + +已知: + 18:42:41 机器异常关机(Kernel-Power 41 / EventLog 6008,无蓝屏记录) + 19:58:23 重新启动 + 20:01:37 explorer 起来 + 自启项里没有 ykxmz,当前也没有 ykxmz 在跑 + +要查的是:现在屏幕上到底有什么、是不是有全屏窗口盖住了桌面。 +''' +from __future__ import annotations + +import ctypes +import ctypes.wintypes as wt +import subprocess + +user32 = ctypes.WinDLL("user32", use_last_error=True) + +EnumWindows = user32.EnumWindows +EnumWindows.argtypes = [ctypes.WINFUNCTYPE(wt.BOOL, wt.HWND, wt.LPARAM), + wt.LPARAM] + + +class RECT(ctypes.Structure): + _fields_ = [("left", wt.LONG), ("top", wt.LONG), + ("right", wt.LONG), ("bottom", wt.LONG)] + + +def windows() -> list: + out = [] + + def cb(hwnd, _lp): + if not user32.IsWindowVisible(hwnd): + return True + title = ctypes.create_unicode_buffer(512) + user32.GetWindowTextW(hwnd, title, 512) + cls = ctypes.create_unicode_buffer(256) + user32.GetClassNameW(hwnd, cls, 256) + r = RECT() + user32.GetWindowRect(hwnd, ctypes.byref(r)) + pid = wt.DWORD() + user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid)) + w, h = r.right - r.left, r.bottom - r.top + if w > 80 and h > 80: + out.append({"hwnd": hwnd, "title": title.value, "cls": cls.value, + "pid": pid.value, "w": w, "h": h, + "x": r.left, "y": r.top}) + return True + + EnumWindows(ctypes.WINFUNCTYPE(wt.BOOL, wt.HWND, wt.LPARAM)(cb), 0) + return out + + +def proc_name(pid: int) -> str: + try: + r = subprocess.run( + ["powershell", "-NoProfile", "-Command", + "(Get-Process -Id %d -EA SilentlyContinue).ProcessName" % pid], + capture_output=True, text=True, timeout=8) + return (r.stdout or "").strip() or "?" + except Exception: # noqa: BLE001 + return "?" + + +def main() -> int: + sw = user32.GetSystemMetrics(0) + sh = user32.GetSystemMetrics(1) + print(" 屏幕: %d x %d" % (sw, sh)) + print(" 当前输入桌面: %s" % _desktop_name()) + print() + + ws = windows() + ws.sort(key=lambda w: w["w"] * w["h"], reverse=True) + print(" 可见窗口 %d 个(按面积排序,前 20):" % len(ws)) + for w in ws[:20]: + full = w["w"] >= sw - 4 and w["h"] >= sh - 4 + pn = proc_name(w["pid"]) + print(" %s %-14s pid=%-6d %dx%d @(%d,%d)" + % ("★全屏" if full else " ", pn, w["pid"], + w["w"], w["h"], w["x"], w["y"])) + print(" 类=%-22s 标题=%r" % (w["cls"][:22], w["title"][:60])) + print() + fulls = [w for w in ws if w["w"] >= sw - 4 and w["h"] >= sh - 4] + if fulls: + print(" ⚠ 有 %d 个全屏窗口 —— 下面这些就是盖住桌面的嫌疑:" % len(fulls)) + for w in fulls: + print(" pid=%d (%s) 类=%s 标题=%r" + % (w["pid"], proc_name(w["pid"]), w["cls"], w["title"][:50])) + else: + print(" ✓ 没有全屏窗口盖着") + print() + ex = subprocess.run(["powershell", "-NoProfile", "-Command", + "Get-Process explorer -EA SilentlyContinue | " + "Select-Object -First 1 -ExpandProperty Id"], + capture_output=True, text=True, timeout=10) + print(" explorer PID: %s" % ((ex.stdout or "").strip() or "**没在跑**")) + return 0 + + +def _desktop_name() -> str: + """当前输入桌面的名字。Default = 正常,Winlogon = 锁屏。""" + h = user32.OpenInputDesktop(0, False, 0x0001) + if not h: + return "(拿不到 —— 可能锁屏或没有交互桌面)" + try: + sb = ctypes.create_unicode_buffer(256) + need = wt.DWORD() + ok = user32.GetUserObjectInformationW(h, 2, sb, 512, + ctypes.byref(need)) + return sb.value if ok else "(拿不到名字)" + finally: + user32.CloseDesktop(h) + + +if __name__ == "__main__": + import sys + sys.exit(main()) diff --git a/_build/watch_stability.py b/_build/watch_stability.py new file mode 100644 index 0000000..d1944b2 --- /dev/null +++ b/_build/watch_stability.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +r'''稳定性观测:连续打公网,统计可用率。 + +用途:验证 2.1.2 的隧道加固(协议 auto + 重试拉大)真的有效。 +2.1.1 之前是"过一阵就 530",所以只看一次 200 说明不了问题, +要**连续观测一段时间**。 + +用法: + python _build/watch_stability.py # 默认 20 次、间隔 15 秒 + python _build/watch_stability.py 40 20 # 40 次、间隔 20 秒 +''' +from __future__ import annotations + +import sys +import time +import urllib.error +import urllib.request + +URL = "https://relay.xxmxxs.top/v1/health" + + +def once(timeout: float = 15.0) -> tuple: + """返回 (状态码文本, 耗时秒)。""" + t0 = time.time() + try: + req = urllib.request.Request( + URL, headers={"Cache-Control": "no-cache", + "User-Agent": "ykxmz-watch"}) + with urllib.request.urlopen(req, timeout=timeout) as resp: + resp.read(64) + return str(resp.status), time.time() - t0 + except urllib.error.HTTPError as exc: + try: + exc.read(64) + except Exception: # noqa: BLE001 + pass + return str(exc.code), time.time() - t0 + except Exception as exc: # noqa: BLE001 + return "无响应(%s)" % type(exc).__name__, time.time() - t0 + + +def main() -> int: + times = int(sys.argv[1]) if len(sys.argv) > 1 else 20 + gap = float(sys.argv[2]) if len(sys.argv) > 2 else 15.0 + + print("=" * 58) + print(" 公网稳定性观测:%d 次,间隔 %.0f 秒(约 %.0f 分钟)" + % (times, gap, times * gap / 60)) + print("=" * 58) + + codes = [] + durs = [] + for i in range(1, times + 1): + code, dur = once() + codes.append(code) + durs.append(dur) + mark = "✓" if code == "200" else "✗" + print(" %s 第 %2d 次: %-8s %.2fs" % (mark, i, code, dur)) + if i < times: + time.sleep(gap) + + ok = sum(1 for c in codes if c == "200") + print() + print("=" * 58) + print(" 200 的次数: %d / %d" % (ok, times)) + print(" 可用率: %.1f%%" % (100.0 * ok / max(1, times))) + if durs: + sd = sorted(durs) + print(" 延迟 中位: %.2fs P90: %.2fs 最大: %.2fs" + % (sd[len(sd) // 2], sd[min(len(sd) - 1, int(len(sd) * 0.9))], + sd[-1])) + print() + if ok == times: + print(" ✅ 全程 200 —— 加固有效") + elif ok >= times * 0.9: + print(" ⚠ 偶尔失败(%d 次)—— 比加固前好,但还没完全稳" + % (times - ok)) + else: + print(" ❌ 可用率偏低(%d 次失败)—— 还要继续查" + % (times - ok)) + return 0 + + +if __name__ == "__main__": + sys.exit(main())