Class ServerMonitorManager

java.lang.Object
com.ultikits.ultitools.manager.ServerMonitorManager

@Internal public class ServerMonitorManager extends Object
服务器监控管理器 负责收集服务器状态信息并通过WebSocket发送
  • Field Details

    • webSocketClient

      private UltiPanelWebSocketClient webSocketClient
    • scheduler

      private ScheduledExecutorService scheduler
      发送线程池。不能是 finalstopMonitoring() 会 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

      private volatile ServerMonitorManager.ServerStateSnapshot 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

      public void setWebSocketClient(UltiPanelWebSocketClient client)
      设置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

      private String extractVersionNumber(String fullVersion)
      提取版本号
    • getCPUUsage

      private double getCPUUsage()
      获取CPU使用率(返回缓存值,由定期采样更新)
    • sampleCpuUsage

      private void sampleCpuUsage()
      采样CPU使用率并更新缓存值
    • sendServerStatusWithRequestId

      public void sendServerStatusWithRequestId(String requestId)
      发送带请求ID的服务器状态信息(响应后端请求)
    • sampleServerState

      private ServerMonitorManager.ServerStateSnapshot sampleServerState()
      在主线程上采一份服务器状态快照。

      只能在 Bukkit 主线程调用。非主线程调用会被拒绝并记 SEVERE —— 这是防御性的第二道 闸:调度已经保证了线程,但这个类的历史正是「契约被识别了,只应用了一半」,把契约写进 代码里比写进注释里可靠。

      Returns:
      新的快照;若不在主线程则返回 null
    • isOnPrimaryThreadOrComplain

      private boolean isOnPrimaryThreadOrComplain()
      线程契约的唯一检查点:不在主线程就记 SEVERE 并返回 false。

      这个类的历史正是「契约被识别了,只应用了一半」,所以把契约写成运行时可观测的信号, 比写进注释可靠——真机上只要日志里出现这句,就说明调度被改错了。

    • refreshStateSnapshot

      void refreshStateSnapshot()
      主线程定时任务的入口:采样并发布快照。

      包级可见,供测试直接驱动。

    • hasSampledState

      boolean hasSampledState()
      供测试断言「到底采过样没有」——比从外部观察发出去的 JSON 更直接。
    • currentSnapshot

      private ServerMonitorManager.ServerStateSnapshot 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

      public void sendMetricsDataWithRequestId(String requestId)
      发送性能统计数据(带请求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()