Class ServerMonitorManager
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final class一次主线程采样的结果。字段全部 final,构造完成后不再改动,通过 volatile 字段发布, 因此读线程看到的要么是上一份完整快照、要么是这一份完整快照,不会看到半份。 -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate intprivate booleanprivate doubleprivate longprivate ScheduledExecutorService发送线程池。不能是 final:stopMonitoring()会 shutdown 它,而ScheduledExecutorService一经 shutdown 就永久失效。logout 之后再 login 是一条完全正常的路径,那时startMonitoring()必须拿到一个可用的池, 否则scheduleAtFixedRate直接抛RejectedExecutionException。private static final long世界/玩家/插件状态的采样周期,单位 tick。100 tick = 5 秒,与 batch_update 的发送节拍一致。private org.bukkit.scheduler.BukkitTask主线程采出来的服务器状态快照,异步发送线程只读它。private intprivate final long[]private final long[]private final long[]private org.bukkit.scheduler.BukkitTask主线程上的两个定时任务,stopMonitoring()需要能取消它们。private UltiPanelWebSocketClient -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate doublecalculateAverageTPS(long[] history, int count) 计算历史TPS的平均值private double计算实时TPS(基于最近的历史记录)private double[]计算TPS - 返回 [1分钟, 5分钟, 15分钟] 平均值private static voidcancelTask(org.bukkit.scheduler.BukkitTask task) 取当前快照供发送线程使用。private StringextractVersionNumber(String fullVersion) 提取版本号private double获取CPU使用率(返回缓存值,由定期采样更新)private com.google.gson.JsonObject获取当前性能统计数据private com.google.gson.JsonArray获取当前插件列表数组private com.google.gson.JsonObject获取当前服务器状态数据doubleGet current 1-minute average TPS.(package private) boolean供测试断言「到底采过样没有」——比从外部观察发出去的 JSON 更直接。booleanprivate boolean线程契约的唯一检查点:不在主线程就记 SEVERE 并返回 false。(package private) void主线程定时任务的入口:采样并发布快照。private void采样CPU使用率并更新缓存值在主线程上采一份服务器状态快照。private void发送batch_update消息,合并status、metrics、plugins、logs到单条WebSocket帧 每5秒调用一次,plugins每12个tick(60秒)包含一次void发送性能统计数据(独立消息,用于on-demand请求)voidsendMetricsDataWithRequestId(String requestId) 发送性能统计数据(带请求ID,用于响应后端请求)voidsendPlayerEvent(String eventType, org.bukkit.entity.Player player, com.google.gson.JsonObject additionalData) 发送玩家事件void发送服务器状态信息voidsendServerStatusWithRequestId(String requestId) 发送带请求ID的服务器状态信息(响应后端请求)void设置WebSocket客户端void开始监控服务器状态void停止监控private void更新TPS计算 + CPU采样 - 每秒执行一次(20 ticks)
-
Field Details
-
webSocketClient
-
scheduler
发送线程池。不能是 final:stopMonitoring()会 shutdown 它,而ScheduledExecutorService一经 shutdown 就永久失效。logout 之后再 login 是一条完全正常的路径,那时startMonitoring()必须拿到一个可用的池, 否则scheduleAtFixedRate直接抛RejectedExecutionException。 -
isMonitoring
private boolean isMonitoring -
tickCount
private int tickCount -
lastTick
private long lastTick -
tpsHistory1m
private final long[] tpsHistory1m -
tpsHistory5m
private final long[] tpsHistory5m -
tpsHistory15m
private final long[] tpsHistory15m -
historyIndex
private int historyIndex -
lastCpuUsage
private volatile double lastCpuUsage -
SNAPSHOT_INTERVAL_TICKS
private static final long SNAPSHOT_INTERVAL_TICKS世界/玩家/插件状态的采样周期,单位 tick。100 tick = 5 秒,与 batch_update 的发送节拍一致。- See Also:
-
stateSnapshot
主线程采出来的服务器状态快照,异步发送线程只读它。这是 issue #179 的全部要点。在它存在之前,
sendBatchUpdate跑在普通ScheduledThreadPool上,却在那里直接调Bukkit.getWorlds()、world.getLoadedChunks()、Bukkit.getOnlinePlayers()、player.getLocation()—— 全是 Paper 明确不支持在异步线程上碰的可变世界状态, 表现为偶发的并发修改异常或读到撕裂的数据。同一个类里的 TPS/CPU 采样已经正确地 hop 到了
runTaskTimer,说明契约当时 就被识别到了,只是只应用了一半。本字段把剩下那一半补齐,沿用完全相同的模式。代价是数据最多陈旧一个采样周期(5 秒)。这是刻意选的:另一条路是让异步线程用
callSyncMethod().get()同步等主线程,那会让监控的存活依赖主线程的健康度, 而服务器卡顿时恰恰是最需要监控还能说话的时候。 -
tpsTask
private org.bukkit.scheduler.BukkitTask tpsTask主线程上的两个定时任务,stopMonitoring()需要能取消它们。 -
snapshotTask
private org.bukkit.scheduler.BukkitTask snapshotTask
-
-
Constructor Details
-
ServerMonitorManager
public ServerMonitorManager()
-
-
Method Details
-
setWebSocketClient
设置WebSocket客户端- Parameters:
client- WebSocket客户端
-
startMonitoring
public void startMonitoring()开始监控服务器状态 -
stopMonitoring
public void stopMonitoring()停止监控除了关掉发送线程,还要取消两个主线程定时任务。原先这里只
scheduler.shutdown(), 那在采样搬到主线程之前是够的——遍历世界那份开销本来就长在发送线程上,关掉发送 就一起没了。搬家之后不取消的话,「停止监控」会变成「不再发送、但照样每 5 秒遍历一遍 所有世界和区块」,而且是在主线程上。见 PR #265 的评审。 -
cancelTask
private static void cancelTask(org.bukkit.scheduler.BukkitTask task) -
sendServerStatus
public void sendServerStatus()发送服务器状态信息 -
extractVersionNumber
提取版本号 -
getCPUUsage
private double getCPUUsage()获取CPU使用率(返回缓存值,由定期采样更新) -
sampleCpuUsage
private void sampleCpuUsage()采样CPU使用率并更新缓存值 -
sendServerStatusWithRequestId
发送带请求ID的服务器状态信息(响应后端请求) -
sampleServerState
在主线程上采一份服务器状态快照。只能在 Bukkit 主线程调用。非主线程调用会被拒绝并记 SEVERE —— 这是防御性的第二道 闸:调度已经保证了线程,但这个类的历史正是「契约被识别了,只应用了一半」,把契约写进 代码里比写进注释里可靠。
- Returns:
- 新的快照;若不在主线程则返回
null
-
isOnPrimaryThreadOrComplain
private boolean isOnPrimaryThreadOrComplain()线程契约的唯一检查点:不在主线程就记 SEVERE 并返回 false。这个类的历史正是「契约被识别了,只应用了一半」,所以把契约写成运行时可观测的信号, 比写进注释可靠——真机上只要日志里出现这句,就说明调度被改错了。
-
refreshStateSnapshot
void refreshStateSnapshot()主线程定时任务的入口:采样并发布快照。包级可见,供测试直接驱动。
-
hasSampledState
boolean hasSampledState()供测试断言「到底采过样没有」——比从外部观察发出去的 JSON 更直接。 -
currentSnapshot
取当前快照供发送线程使用。若还一次都没采过、而调用方恰好就在主线程上,就地补采一次——否则连接建立后的第一帧 会是一片零,以及
sendServerStatusWithRequestId这类按需请求在监控启动前会返回空数据。 -
getCurrentServerStatusData
private com.google.gson.JsonObject getCurrentServerStatusData()获取当前服务器状态数据所有 Bukkit 派生的字段都取自主线程采好的快照(见
stateSnapshot); 本方法本身可以在任意线程调用。Runtime 内存、JVM 运行时长、TPS、CPU 不属于 Bukkit 状态, 就地读取即可——TPS 与 CPU 早已由主线程的 1Hz 任务维护。 -
sendBatchUpdate
private void sendBatchUpdate()发送batch_update消息,合并status、metrics、plugins、logs到单条WebSocket帧 每5秒调用一次,plugins每12个tick(60秒)包含一次 -
getCurrentPluginArray
private com.google.gson.JsonArray getCurrentPluginArray()获取当前插件列表数组取自主线程采好的快照。
Bukkit.getPluginManager().getPlugins()同样不该在异步线程上遍历。 -
getCurrentMetricsData
private com.google.gson.JsonObject getCurrentMetricsData()获取当前性能统计数据Bukkit 派生的字段取自快照;Runtime 内存与 TPS 就地计算。
-
sendMetricsData
public void sendMetricsData()发送性能统计数据(独立消息,用于on-demand请求) -
sendMetricsDataWithRequestId
发送性能统计数据(带请求ID,用于响应后端请求) -
updateTpsAndCpu
private void updateTpsAndCpu()更新TPS计算 + CPU采样 - 每秒执行一次(20 ticks) -
getCurrentTPS
public double getCurrentTPS()Get current 1-minute average TPS.- Returns:
- the 1-minute average TPS
- Since:
- 6.2.3
-
calculateTPS
private double[] calculateTPS()计算TPS - 返回 [1分钟, 5分钟, 15分钟] 平均值 -
calculateAverageTPS
private double calculateAverageTPS(long[] history, int count) 计算历史TPS的平均值 -
calculateRealtimeTPS
private double calculateRealtimeTPS()计算实时TPS(基于最近的历史记录) -
sendPlayerEvent
public void sendPlayerEvent(String eventType, org.bukkit.entity.Player player, com.google.gson.JsonObject additionalData) 发送玩家事件 -
isMonitoring
public boolean isMonitoring()
-