
LagMap
一个服务端Minecraft模组,回答了性能分析器通常回避的问题:现在究竟是哪个维度、区块和基地在加载服务器?
LagMap
一个服务端 Minecraft 模组,回答了分析器通常回避的问题:
当前是哪个维度、区块和基地在给服务器造成负载?
像 spark 这样的工具会告诉你什么代码很慢。LagMap 则会告诉你
世界中的哪个位置在进行大量工作。它会定时对每个已加载区块进行采样,根据估算的
负载评分对它们进行排序,并生成一个快照,你可以在聊天栏、JSON 文件或独立的
HTML 报告中查看。
- Minecraft 版本: 1.20.1
- 加载器: Forge 47.x
- 端类型: 仅服务端(专用服务器或单人游戏中的内置服务器)
- 是否需要客户端模组: 否
功能说明
- 刻计时。 每刻测量 MSPT,并维护滚动的 10 秒 / 30 秒 / 60 秒窗口
(平均值、最小值、最大值),以及由此得出的 TPS 数值。
2. 按维度和区块采样。 每 N 秒(默认 5 秒)遍历每个维度的
已加载区块并记录:
- 实体数量,以及其中有多少实体位于实体刻 tick 的区块中
- 方块实体数量,以及其中有多少带有 ticker 且位于
tick 的区块中
- 区块是否被强制加载(这些区块会绕过评分阈值,不过空的
区块仍然会排在前 N 名列表之外;每个维度的
forceLoadedChunkCount始终报告总数)
- 区块中主要的实体 / 方块实体类型
- 可配置区块半径内的玩家
3. 启发式负载评分,针对每个区块和每个维度(见下方警告)。
4. 导出。 生成稳定的 JSON 快照和静态 HTML 报告,在
服务端线程之外以原子方式写入。
尚未实现的功能
- 不测量真实的每区块或每方块实体刻成本。请参阅
ROADMAP.md。
- 没有客户端 GUI、没有实时网页仪表板、没有游戏内地图覆盖层。
- 没有特定模组集成(紧凑机器、FTB 区块、领地声明、spark 导入)。
- 没有历史时间序列——磁盘上只保留最新的快照。
- 多版本支持不在本版本范围内;请参阅下方移植部分。
该评分是估算值,而非测量值
estimatedScore是区块内容的加权计数。一个满是 tick 方块实体的
区块评分会很高,因为这类区块通常消耗刻时间——但单个
病态实体可能消耗超过一千个漏斗,而 LagMap 无法察觉。请使用
该评分来决定接下来该查看哪里,然后用 spark 确认。每次导出都会重复
此免责声明,以确保报告被粘贴到 Bug 追踪器时不会丢失。
命令
默认情况下,所有命令都需要权限等级 2(可配置,请参阅 permissionLevel)。
| 命令 | 效果 |
|---|---|
/lagmap status |
显示 MSPT 窗口、TPS、采样器状态,以及上次采样的各维度总计。 |
/lagmap top [count] |
显示服务端范围内上次采样中最热的区块。count 默认为 10,最大 100。 |
/lagmap dump |
立即采样并写入 latest.json 和 latest.html,不受配置影响。 |
/lagmap start |
开始周期性采样。 |
/lagmap stop |
停止周期性采样。刻计时继续运行。 |
/lagmap tp <dimension> <chunkX> <chunkZ> |
将你传送到该区块的中心。仅限玩家使用。 |
典型的排查流程:
/lagmap status # MSPT 是否真的很高?哪个维度负载最重?
/lagmap dump # 立刻获取一份新样本
/lagmap top 20 # 哪些区块在该维度中占主导?
/lagmap tp minecraft:overworld 145 -302
/lagmap tp 从地表高度图推导 Y 坐标,这在类主世界维度中有意义,
在下界或自定义维度中则会产生误导——它会发出警告。传送会加载目标区块,
这是 LagMap 唯一会导致区块加载的地方。
输出文件
写入相对于服务器目录的 config/lagmap/ 中:
| 文件 | 内容 |
|---|---|
latest.json |
机器可读的快照。键顺序稳定,schemaVersion: 1。 |
latest.html |
内嵌快照的独立报告。双击即可打开。 |
两者都先写入临时文件再移动到目标位置,因此轮询 JSON 的查看者
永远不会读取到写入一半的文档。
本仓库中的 web/viewer.html 是同一个查看器,但没有内置数据:打开它,
将任何 latest.json 拖放到上面即可。这对于阅读别人发给你的快照很有用。
阅读 JSON
{
"schemaVersion": 1,
"timestamp": "2026-08-20T12:00:00Z",
"chunkEnumeration": "chunkmap (complete: every fully-loaded chunk)",
"scoreFormula": "estimated (idleEntities*0.10) + ...",
"mspt": {
"last": 48.20,
"tps10s": 20.00,
"last10s": { "samples": 200, "average": 45.10, "min": 38.00, "max": 91.40 },
"last30s": { ... },
"last60s": { ... }
},
"totals": { "dimensions": 3, "loadedChunks": 1841, "entities": 2204, "blockEntities": 9033 },
"dimensions": [ { "dimensionId": "...", "totalEstimatedScore": 0, "hottestChunks": [ ... ] } ],
"topChunks": [ { "dimensionId": "...", "chunkX": 0, "chunkZ": 0, "estimatedScore": 0, ... } ]
}
值得了解的字段:
chunkEnumeration— 已加载区块的发现方式。chunkmap表示列表是
完整的。fallback表示由特殊模组 ticket 保持打开的区块可能会缺失;请将
该报告视为下限值。
-entityCount对比tickingEntityCount— 超出 tick 范围的区块仍然保有
其内容,但不消耗刻时间。两者差距大意味着“内容很多,但目前并不昂贵”。
-scannedChunkCount对比loadedChunkCount— 这两者通常不同,差距并非
bug。loadedChunkCount是 Minecraft 自己的区块持有者计数,包括仅部分生成
或位于加载区域边缘的区块。
scannedChunkCount统计的是 LagMap 实际检查的完全加载的区块——
只有这些区块才包含实体和方块实体。只有当scannedChunkCount等于
maxChunksPerDimension时,差距才表示有截断。
-sampleDurationMicros— LagMap 自身花费的时间。注意此项;工具自身
不能成为卡顿源。
配置
config/lagmap.toml,首次启动时生成。要点:
| 键 | 默认值 | 说明 |
|---|---|---|
sampling.enabledOnStart |
true |
自动开始采样。 |
sampling.sampleIntervalSeconds |
5 |
更低的值响应更快但开销更大。 |
sampling.maxChunksPerDimension |
20000 |
每次采样每个维度的安全上限。 |
sampling.collectTypeBreakdown |
true |
在大型服务器上可关闭以降低采样成本。 |
sampling.nearbyPlayerRadiusChunks |
8 |
用于将玩家归属到热点区块的半径。 |
reporting.topChunksGlobal |
25 |
每次快照保留的服务端热点区块数。 |
reporting.writeJsonEachSample |
true |
设为 false 则仅在 /lagmap dump 时写入。 |
reporting.permissionLevel |
2 |
使用 /lagmap 所需的原版权限等级。 |
scoring.*Weight |
见文件 | 启发式评分的权重。 |
评分权重默认为 idleEntity 0.10、idleBlockEntity 0.05、tickingEntity 1.00、tickingBlockEntity 1.50。空闲内容不将权重设为零,因为它们仍消耗内存、区块保存
和网络带宽——只是不消耗刻时间。
性能开销
LagMap 是一个卡顿排查工具,因此它的设计目标就是不给服务器增加卡顿:
- 采样是周期性的,而非每刻运行。只有 MSPT 测量每刻运行,那只是向
预分配环形缓冲区写入两次System.nanoTime()调用。
- 采样只读取已加载的状态。它从不强制加载区块,也从不触碰
磁盘。(/lagmap tp是唯一例外,且只针对目标区块。)
- “这个方块实体是否 tick”按方块状态只回答一次,并在本次会话中缓存。
- 所有文件写入都在低优先级守护线程上进行。
- 每个快照中的sampleDurationMicros准确告诉你一次采样的成本。
构建
需要 JDK 来运行 Gradle。Minecraft 1.20.1 本身需要 Java 17,如果你没有,Gradle 会
通过 foojay 工具链解析器自动下载。
./gradlew build
模组 jar 位于 build/libs/lagmap-1.20.1-0.1.0.jar。
Gradle 8.14 支持 Java 17 到 24。如果你的默认 java 版本更新,请将 Gradle 指向
受支持的 JDK:
JAVA_HOME=/path/to/jdk-21 ./gradlew build
使用 ./gradlew runServer 运行测试服务器(首次启动时在 run/eula.txt 中接受 EULA)。
移植到新版本 / NeoForge
版本特定代码被刻意隔离:
| 包 | 依赖 Minecraft? | 说明 |
|---|---|---|
com.lagmap.model |
否 | 纯记录。 |
com.lagmap.core |
否 | 刻窗口、采样调度。 |
com.lagmap.score |
否 | 仅评分。 |
com.lagmap.export |
否 | JSON + HTML。 |
com.lagmap.platform |
否 | SnapshotCollector 接缝。 |
com.lagmap.platform.forge1201 |
是 | 1.20.1 实现。 |
com.lagmap.command、LagMapMod、LagMapConfig |
是 | Brigadier、Forge 事件、Forge 配置。 |
移植意味着编写一个新的 SnapshotCollector、重新接入入口点、适配命令/配置类。
其他一切原样迁移。
模组中唯一使用反射的代码位于platform/forge1201/LoadedChunkAccess.java:ChunkMap#getChunks() 是受保护的,且没有
公共等效方法。它在类初始化时解析一次(同时尝试 Mojang 映射名和 SRG 名),
如果失败则回退到公共 API 扫描。采样路径本身不进行任何名称查找。
项目文档
| 文件 | 用途 |
|---|---|
README.md |
什么是 LagMap 以及如何使用(本文件)。 |
ROADMAP.md |
计划中的工作,按价值与投入比排序。长期愿望清单。 |
MEMORY.md |
当前开发状态:什么是已验证的、什么是未测试的、下一步做什么。每次会话更新。 |
DECISIONS.md |
代码为何如此设计的原因。仅追加。 |
CLAUDE.md |
面向 AI 编码代理的仓库特定指南。 |
许可证
MIT。
正在加载版本记录…
正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。