终末地卡顿排查:从 Prometheus 监控到 ETW trace

背景

明日方舟:终末地在 3 月更新之后,我这边开始频繁出现一种很难受的卡顿:

  • 游戏画面会突然变得极不流畅
  • 切个窗口都可能要半分钟
  • 但是直觉上又不像是常见的 CPU、内存或者显卡直接打满

这种问题最烦的地方就在于,体感很明显,但一眼看监控面板又说不清到底是谁在搞鬼。

既然这样,那就不猜了,直接把监控拉满,准备抓一次现行。

先上基础监控

我最先部署的是一套比较通用的组合:

  • windows_exporter
  • Prometheus
  • Grafana

目标很简单,先把 CPU、内存、磁盘、显卡之类的基础指标常驻记录下来,等下一次复现的时候再回头看。

一开始的想法其实很朴素:

  • 如果是某个进程突然把 CPU 打满,那很好抓
  • 如果是内存泄漏或者疯狂读盘,也很好抓
  • 就算最后抓不到根因,至少先排除一批最常见的嫌疑人

第一次复现:看起来像磁盘问题

第一次比较像样的复现发生在 2026-03-05 晚上,大概是 19:50 到 19:52。

那一段时间的现象比较明确:

  • CPU 没怎么动
  • 内存没怎么动
  • 但是磁盘出现了一波比较明显的读写

这时候怀疑对象一下子就变了。既然不是算力瓶颈,那就更像是某个进程在后台做了比较重的 I/O。

我接着在 Prometheus 里按时间窗口去查读盘最活跃的进程,结果有点出乎意料:对应时间段里排在前面的不是某个普通应用进程,而是 System。

这件事一下子就麻烦了,因为很多内核态活动都会挂在 System 下面,看到它并不等于已经抓到具体元凶,只能说明:

  • 这不像是一个普通用户态程序自己在疯狂扫盘
  • 问题更可能和驱动、内核组件、文件系统过滤、反作弊之类的东西有关

我当时第一反应就是怀疑反作弊。毕竟游戏相关的内核态活动,本来就很容易让人往这个方向想。

而且说实话,这里面也有一点像是在先把锅甩给 ACE。之前我在群里和论坛里交流时,也有不少人第一时间怀疑是反作弊的问题,毕竟腾讯在这方面的名声实在不算好。

但后面越排查,我越觉得这条线没那么稳。因为我之前玩三角洲的时候,虽然同样是 ACE,但反而从来没因为它遇到过这种卡顿。如果只看我自己的体验,那至少说明一件事:同样是腾讯这套东西,它未必天然就会把机器搞成这样。真要说技术力,这块腾子反而一直不算差。

第二次怀疑:也许是显存或者驱动

后面继续复现时,情况又变得没那么像“扫盘”了。

2026-03-07 那次,我一边跑游戏,一边盯着监控,发现显存占用已经很接近上限了。紧接着机器还蓝屏了一次,正好系统留下了 dump 文件。

把 dump 丢进 WinDbg 之后,得到的结论是:

NVIDIA 显卡驱动 nvlddmkm.sys 在执行内存写入时访问了无效地址,触发了 PAGE_FAULT_IN_NONPAGED_AREA (0x50)

这时候怀疑方向又开始偏向显卡驱动和显存了。

因为如果只是“某个反作弊疯狂扫盘”,那和显卡驱动直接蓝屏之间的联系其实没那么自然;反过来,如果游戏本身在某种情况下把显存、驱动或者渲染链路压进了异常状态,那体感卡顿和蓝屏就能串起来了。

我甚至一度怀疑,是不是终末地在 4K 下把显存压得太狠了,只是之前没有留意过这个方向。

为了继续观察,我一开始其实没打算用 windows_exporter 采显存,因为我先问了几个大模型,得到的结论都是“不行”。

当时包括 GPT-5.4、Claude Opus 4.6、Gemini 3.1 Pro 在内,给我的回答都偏向“windows_exporter 不支持 GPU 相关采集”,所以我原本准备常驻开小飞机来盯显存。

这件事也让我很直观地感受到,AI 在这种“某个工具到底支不支持某个具体能力”的问题上,离真正可靠还差得很远,尤其是涉及版本差异、冷门特性和文档细节的时候。

但小飞机这种方案还是太麻烦了,后面我去翻了官方文档,才发现 windows_exporter 其实有 GPU collector,可以直接把相关指标接进 Prometheus 和 Grafana。这样一来,就比常驻开小飞机和任务管理器方便多了。

第三次复现:又不像显存,也不像磁盘

但接下来的复现又把这个猜想打回去了。

2026-03-09 再次卡顿时,总显存占用大约 13.4G;而更早之前玩别的游戏时,显存甚至到过 15.2G,也没有出现类似问题。

与此同时,那次卡顿期间磁盘也没有什么异常波动。

也就是说,前面两条比较直观的线索:

  • 磁盘异常
  • 显存逼近上限

都开始变得不稳定了。它们像是“有时候会同时出现的伴随现象”,但不太像真正稳定的根因。

到这一步,我对“外部依赖导致”的信心已经下降了很多,开始怀疑问题更接近游戏本身的线程调度、渲染同步或者初始化流程。

最后还是录了 ETW trace

不死心,还是继续抓 trace。

最终我录到了一份可以拿来认真分析的 ETW trace,然后用 WPA 打开,重点看了下面这些视图:

  • CPU Usage (Precise)
  • UI Delays
  • Syscalls
  • Thread Lifetimes
  • Processes
  • Stacks
  • CPU Usage (Sampled)
  • GPU Utilization (FM)

这份 trace 给出的结论,比前面的资源监控更有解释力。

关键结论:这更像“等待型卡顿”

如果只看体感,这种卡顿很容易让人以为是某个硬件打满了。但从 WPA 里看到的模式并不是这样。

更贴切的描述是:

Endfield.exe 的关键线程大部分时间都在等待,只有在某些时刻才会成组被唤醒,然后连续工作一小段时间。

这和“10 秒里画面只动 1 秒”的体感是高度一致的。

CPU Usage (Precise)

这次分析里最有价值的视图之一就是 CPU Usage (Precise)。

从里面能看到一组比较稳定的核心线程,它们在多个时间窗口里都反复出现,而且活动节奏很像同一个引擎调度域里的关键线程。

这组线程的共同特征是:

  • Wait 时间很高
  • Ready 时间不高
  • Ready Max 也不高

这个结果很重要,因为它说明:

  • 不是线程想跑但是抢不到 CPU
  • 也不是系统调度拥塞导致它们长期排队
  • 而是这些线程大部分时间根本就在等某个条件成立

如果真的是 CPU 不够用,通常应该看到 Ready 堆得很高,线程一直想跑却跑不到;但这里不是这种模式。

更像的情况是:

  • 某个同步条件没满足
  • 某个事件没有到来
  • 某条渲染或异步链路没有放行
  • 所以整组核心线程只能等

而在某些时间片里,这些线程又会突然一起连续工作几秒,然后再重新回到等待状态。这说明游戏不是“持续稳定低帧率”,而是“流程卡住,偶尔推进一段”。

UI Delays

UI Delays 也给了很强的侧面证据。

在 Endfield.exe 下面,可以明显看到:

  • MsgCheck Delay
  • Input Delay
  • COM Call
  • Hourglass Cursor

这说明卡顿时不只是画面掉帧,而是前台窗口本身的消息处理和输入响应也被拖慢了。

换句话说,这不是一个单纯的“GPU 忙,所以帧率低”问题,而更像是:

  • 主循环推进受阻
  • UI 线程也被连带拖慢
  • 所以切窗口、处理输入、刷新界面都一起变差

从详细表里还能看到一个 UI / 输入相关线程候选,它在关键时间段里有多次比较明显的 Input Delay 记录。这个线程本身并不是前面那组高活跃核心线程之一,所以更像是:

  • 一组线程负责引擎推进
  • 另一条线程负责窗口和输入
  • 前者卡住之后,后者也被间接拖慢

Syscalls

Syscalls 视图虽然没有给出很完整的函数名和堆栈,但还是能看出另一类线程模式。

在 Endfield.exe 里,可以看到另一批线程出现“Count = 1,但 Duration 很长”的 syscall 记录,也就是:

  • 开始得很早
  • 结束得很晚
  • 中间像是一直挂在某种内核等待上

这不像高频短 syscall,更像是长期阻塞型等待。它和前面 CPU Usage (Precise) 里看到的高 Wait 模式是对得上的。

所以当前更合理的线程结构大概是:

  • 一组核心线程负责游戏推进
  • 一组辅助线程长期阻塞在 syscall / 内核等待上
  • 主流程在某种程度上依赖这些等待链路的结果

CPU Usage (Sampled)

比较可惜的是,这份 trace 里的 CPU sampled 数据基本不可用。

按理说,如果 sampled 数据足够完整,进一步就有机会看到热点到底落在:

  • WaitForSingleObject
  • Present
  • ReadFile
  • D3DCompile
  • 某个第三方 DLL

但这次 trace 里 sampled 视图几乎只剩 Idle,没有办法把问题直接钉到函数级别。

这也是为什么现在只能定性到“等待型卡顿”,还没法精确落到某个模块或某条调用链上。

现在最像什么问题

把上面的线索放在一起,我现在更倾向于下面几个方向。

1. 同步等待或事件等待

这是目前最符合现象的解释。

也就是主线程、渲染线程或者某组 worker,在等某个事件、某个异步结果或者某个流程门闩放行,导致整个游戏推进只能断断续续地进行。

2. 渲染同步或 Present / Fence 类等待

这也是很强的嫌疑方向。

如果某条渲染链路被卡住,例如 Present、GPU fence、交换链路或者显示相关同步异常,就很容易出现:

  • CPU 不忙
  • GPU 面板看着也不一定夸张
  • 但画面推进断断续续
  • UI 和输入一起变差

只是这次 trace 里的 GPU 视图不够细,没法进一步坐实。

3. 启动期初始化链路有依赖卡住

因为问题经常发生在游戏刚启动或者刚进入运行态之后,所以也不能排除某些初始化流程设计得不好,比如:

  • 资源初始化
  • shader / pipeline 初始化
  • 网络或鉴权
  • 反作弊初始化
  • 设备、输入、音频等系统组件初始化

只要主流程里有一步在等待这些异步结果,就可能形成现在这种“偶尔推进一段”的表现。

基本可以排除什么

至少从这几次监控和 trace 来看,下面这些方向已经没那么像了:

CPU 算力不够

不像。因为线程不是长期 Ready 堵塞,而是大量 Wait。

单纯的线程抢不到 CPU

也不像。Ready Max 并不大,不是那种典型的调度拥塞。

简单粗暴的硬件瓶颈

也不太像。CPU、内存、显存、磁盘都出现过“看上去可疑”的时刻,但都没有稳定到足以单独解释全部现象。

这次排查最后得到的结论

目前我对这个问题的判断是:

这次终末地卡顿,更像是游戏内部某条等待链路出了问题,而不是我的机器硬件真的扛不住。

更具体一点说,就是:

  • 不是 CPU 打满
  • 不是内存打满
  • 不是线程抢不到 CPU
  • 也没有证据能稳定指向某个普通应用在后台搞事

真正更像的是:

  • 游戏关键线程在等待
  • UI 和输入线程被连带拖慢
  • 主流程只能间歇推进

前面一度怀疑过反作弊、磁盘扫盘、显存爆掉、显卡驱动,但排查到最后,这些更像是阶段性的可疑线索,不像一个能稳定解释所有复现的根因。

留个阶段性结论

这篇先记到这里。

虽然我还没有把问题精确定位到具体函数、具体 DLL 或具体模块,但至少已经把方向收窄到了“等待型卡顿”,而不是继续在 CPU、内存、磁盘这些大路货指标上瞎猜。

如果后面还能录到一份更完整的 trace,尤其是能带上更有效的 CPU sample、Present / DXGI、GPU queue 和更完整堆栈的版本,再看能不能把锅真正甩到某一条调用链头上。

如果下次再录,我准备直接用下面这条 WPR 命令起一个更全的 profile:

1
wpr -start GeneralProfile -start CPU -start DiskIO -start FileIO -start GPU -start ReferenceSet -detaillevel verbose -filemode

这样至少能把 CPU、磁盘、文件 I/O 和 GPU 相关的信息一次性带上,省得后面再因为 profile 开得不对,拿到一份只能定性、不能定点的 trace。

但这件事现在也差不多到我这台机器的能力边界了。之前录的那个相对简单一点的 trace,录了几分钟就已经吃掉了 37G 磁盘,在 WPA 里看同时还占了我 40G 内存。我一共也就 64G 内存,再继续往上加更多 profile,后面的录制和分析就已经有点跟不上了。

所以后面不继续无脑加码录 trace,不是因为这个方向没价值,而是因为我这台电脑的资源已经不太够支撑更重的采集和分析方案了。

至少到目前为止,可以先下一个不那么冤枉硬件的结论:这锅大概率不在我的机器上。

再主观一点说,排查到这一步,我已经不太想继续给游戏公司“打工”了。玩游戏这么多年,我还真没怎么遇到过这种离谱的性能问题:不是单纯掉帧,不是配置不够,也不是一眼就能看出来的硬件瓶颈,而是要玩家自己一路从基础监控搭到 WPR 和 WPA,最后才能勉强把问题定性到“等待型卡顿”。

如果一个游戏能把普通玩家逼到这种排查深度,那我很难不怀疑,至少在性能稳定性和问题定位能力这件事上,开发和测试阶段就已经欠了太多债。更准确地说,也不是我多想排查这东西,而是我实在不想每次一玩游戏前就得顺手重启一次电脑。