Class ServerMonitorManager
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescription(package private) static final classThe result of one main-thread sample. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate FileThe file whose filesystem backs thediskUsagemetric -- the server root (the working directory the Paper process was started in), matchingFileOperationManager's ownserverRootconvention.private ToLongFunction<File> Seam overFile.getFreeSpace()(WR-03) -- includes space reserved for the filesystem's own root/superuser allocation, matching whatdf's ownUse%column treats as "used" (total minus this value).private intprivate booleanprivate doubleprivate final AtomicLongWall-clock timestamp of the last time queued logs were actually drained into abatch_updatemessage, in external-drain mode (Gate-2 finding: with monitoring enabled -- the shipped default --sendBatchUpdate()runs on its own hardcoded 5-second tick, completely independent ofUltiPanelLogTransmitter's own configured batch-send interval.private longprivate static final longPolling granularity formaybeSendLogsOnly()'s scheduled check, in milliseconds (Gate-2 finding, round 11).private final ObjectGuards the ACTUAL drain-and-send operation (drainAndSendLogsOnly(com.ultikits.ultitools.manager.UltiPanelLogTransmitter)) across all THREE paths that can trigger one:sendBatchUpdate()'s 5-second tick,maybeSendLogsOnly()'s 1-second tick, anddrainLogsNow()'s size-triggered, event-driven call (Gate-2 finding, round 7).private ScheduledExecutorServiceSend thread pool.private static final longSampling period for world/player/plugin status, in ticks.private org.bukkit.scheduler.BukkitTaskA server-state snapshot sampled on the main thread; the async send thread only ever reads it.private intprivate ToLongFunction<File> Seam overFile.getTotalSpace(), so "the filesystem reports zero total space" (D-14) is deterministic in tests without needing an actual zero-capacity filesystem.private final long[]private final long[]private final long[]private org.bukkit.scheduler.BukkitTaskThe two main-thread scheduled tasks;stopMonitoring()needs to be able to cancel them.private ToLongFunction<File> Seam overFile.getUsableSpace(), same rationale astotalSpaceReader.private UltiPanelWebSocketClient -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate doublecalculateAverageTPS(long[] history, int count) Calculates the average of a history-of-TPS array.private doubleCalculates the realtime TPS (based on the most recent history entry).private double[]Calculates TPS -- returns the [1-minute, 5-minute, 15-minute] averages.private static voidcancelTask(org.bukkit.scheduler.BukkitTask task) private booleanclaimLogFlushWindow(long now, int intervalMs) Atomically claims the current log-flush window if (and only if)intervalMshas elapsed since the last successful claim, advancinglastLogFlushMstonowas part of the same compare-and-set.private doubleThe used percentage of the filesystem holdingdiskUsageRoot, matchingdf's ownUse%convention (WR-03) rather than a straight(total - usable) / total:used = total - free(viafreeSpaceReader,File.getFreeSpace()-- INCLUDES space reserved for the filesystem's own root/superuser allocation), and the percentage isused / (used + avail)withavailfromusableSpaceReader(File.getUsableSpace()-- EXCLUDES that reservation).(package private) static intcountEnabledPlugins(com.google.gson.JsonArray plugins) Counts entries inpluginswhoseenabledfield istrue-- the per-plugin flagsampleServerState()already captures for every installed plugin (D-14, #437:enabledPluginsmust count plugins that are enabled, not every plugin installed).Gets the current snapshot for the send thread to use.(package private) ServerMonitorManager.ServerStateSnapshotReads the published main-thread sample for the v2 telemetry encoders (AgentTelemetry).private voiddrainAndSendLogsOnly(UltiPanelLogTransmitter transmitter) Drains up totransmitter's configuredbatchSizeand, if non-empty, sends abatch_updatemessage carrying ONLYdata.logs-- shared bymaybeSendLogsOnly()anddrainLogsNow()(Gate-2 finding, round 6 extraction).(package private) voidImmediately drains and sends whatever is currently queued, without waiting for eithersendBatchUpdate()'s 5-second tick ormaybeSendLogsOnly()'s 1-second tick (Gate-2 finding, round 6).private StringextractVersionNumber(String fullVersion) Extract the version number.private doubleGet the CPU usage (returns the cached value, updated by periodic sampling).private com.google.gson.JsonObjectGets the current performance metrics data.private com.google.gson.JsonArrayGets the current plugin list array.private com.google.gson.JsonObjectGets the current server status data.doubleGet current 1-minute average TPS.(package private) ObjectExposeslogDrainLocksoLogStreamManager.initialize(com.ultikits.ultitools.websocket.UltiPanelWebSocketClient)can wireUltiPanelLogTransmitter.setExternalDrainCoordinationLock(java.lang.Object)to the SAME lock object this class's own drain paths already synchronize on (Gate-2 finding, round 9).(package private) booleanLets tests assert whether sampling has actually happened yet -- more direct than observing the emitted JSON externally.booleanDeprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself.private booleanThe single check point for the thread contract: logs SEVERE and returns false when not on the main thread.private voidChecks the log-drain interval gate at aLOG_FLUSH_POLL_INTERVAL_MSgranularity (narrowed from 1 second in round 11), independent ofsendBatchUpdate()'s own fixed 5-second tick (Gate-2 finding, round 3).(package private) booleanStarts the TPS clock now without recording a sample (plan 22-22 fix): the clock otherwise starts when this object is created, and the v2 sampler first runs 20 ticks after the first connection, which may come long after the plugin enabled; that gap would be recorded as the first TPS sample.(package private) voidEntry point for the main-thread scheduled task: samples and publishes the snapshot.(package private) booleanSamples and publishes one state snapshot without the v1 connection gaterefreshStateSnapshot()applies, so the v2 telemetry path and its tests can publish a fresh sample while no v1 socket exists.private voidSample CPU usage and update the cached value.(package private) doubleSamples a server-state snapshot on the main thread.(package private) booleanRuns one TPS and CPU sample on the main thread, without starting the v1 sender, the log drain or either sampling taskstartMonitoring()owns.private voidSends the batch_update message, merging status, metrics, plugins and logs into a single WebSocket frame.private voidsendKeepingUndeliveredLogs(com.google.gson.JsonObject message, UltiPanelLogTransmitter transmitter, com.google.gson.JsonArray logs) Sends abatch_updateframe and, when it carried log records and did not go out, hands them back to the transmitter so the next drain sends them before anything newer (#486).voidDeprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself.voidsendMetricsDataWithRequestId(String requestId) Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself.voidsendPlayerEvent(String eventType, org.bukkit.entity.Player player, com.google.gson.JsonObject additionalData) Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) reports player events itself.voidDeprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself.voidsendServerStatusWithRequestId(String requestId) Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself.(package private) voidsetDiskSpaceReaders(ToLongFunction<File> totalSpaceReader, ToLongFunction<File> freeSpaceReader, ToLongFunction<File> usableSpaceReader) Test seam: inject fake total/free/usable-space readers (e.g.(package private) voidsetDiskUsageRoot(File diskUsageRoot) Test seam: point the disk-usage reading at a different root (e.g.voidDeprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework never calls it, and its own UltiCloud session (protocol 2.0.0) carries the panel requests.voidDeprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself.voidDeprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself.private voidUpdates the TPS calculation + CPU sampling -- runs once per second (20 ticks).
-
Field Details
-
webSocketClient
-
scheduler
Send thread pool. Must not be final:stopMonitoring()shuts it down, and once aScheduledExecutorServiceis shut down it is permanently dead. Logging out and back in is a perfectly normal path, and at that pointstartMonitoring()must obtain a usable pool -- otherwisescheduleAtFixedRatethrowsRejectedExecutionExceptiondirectly. -
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_TICKSSampling period for world/player/plugin status, in ticks. 100 ticks = 5 seconds, matching batch_update's send cadence.- See Also:
-
LOG_FLUSH_POLL_INTERVAL_MS
private static final long LOG_FLUSH_POLL_INTERVAL_MSPolling granularity formaybeSendLogsOnly()'s scheduled check, in milliseconds (Gate-2 finding, round 11). Narrowed from 1 second to bound the worst-case overshoot past a configuredbatchConfig.intervalto roughly this value, rather than up to a full second -- see the call site's own comment for the numeric evidence and the reasoning for not instead dynamically rescheduling this task to track the transmitter's own live interval.- See Also:
-
stateSnapshot
A server-state snapshot sampled on the main thread; the async send thread only ever reads it.This is the entire point of issue #179. Before this field existed,
sendBatchUpdateran on a plainScheduledThreadPoolbut calledBukkit.getWorlds(),world.getLoadedChunks(),Bukkit.getOnlinePlayers(),player.getLocation()directly from there -- all mutable world state Paper explicitly does not support touching from an async thread, surfacing as intermittent concurrent-modification exceptions or torn reads.TPS/CPU sampling in this same class already correctly hopped over to
runTaskTimer, showing the contract was recognized at the time -- just only half applied. This field fills in the other half, following the exact same pattern.The cost is that data can be stale by up to one sampling period (5 seconds). This is a deliberate choice: the alternative would have the async thread synchronously wait on the main thread via
callSyncMethod().get(), which would make monitoring's liveness depend on the main thread's health -- precisely when the server is lagging is exactly when monitoring most needs to still be able to speak. -
tpsTask
private org.bukkit.scheduler.BukkitTask tpsTaskThe two main-thread scheduled tasks;stopMonitoring()needs to be able to cancel them. -
snapshotTask
private org.bukkit.scheduler.BukkitTask snapshotTask -
lastLogFlushMs
Wall-clock timestamp of the last time queued logs were actually drained into abatch_updatemessage, in external-drain mode (Gate-2 finding: with monitoring enabled -- the shipped default --sendBatchUpdate()runs on its own hardcoded 5-second tick, completely independent ofUltiPanelLogTransmitter's own configured batch-send interval. Before this field existed, that meant the panel's livebatchConfig.intervalaction (#432) had NO observable effect whenever monitoring was on, because the monitor drained the queue every 5 seconds regardless of what interval was configured. Gating the drain itself by this timestamp makes the configured interval govern how often logs actually go out, even thoughsendBatchUpdate()itself still ticks every 5 seconds for status/metrics). Starts at0so the very first tick afterstartMonitoring()always drains.AtomicLong, not a plainvolatile long(Gate-2 finding, round 4): two independently-scheduled tasks (sendBatchUpdate()'s 5-second tick andmaybeSendLogsOnly()'s 1-second tick) can become due in the SAME window (e.g. a 1000ms configured interval), andvolatileonly guarantees visibility, not that a "read, compare, then write" sequence is atomic -- both threads could pass the elapsed-time check before either updates the field, both callUltiPanelLogTransmitter.drainQueue(int)concurrently against the same queue, and split one batch of records across two separatelog_batch/batch_updateframes with interleavedpoll()calls, scrambling delivery order across the two frames.claimLogFlushWindow(long, int)usesAtomicLong.compareAndSet(long, long)so only ONE of the two competing callers ever wins a given window; the loser skips draining entirely rather than draining a partial, racing subset. -
logDrainLock
Guards the ACTUAL drain-and-send operation (drainAndSendLogsOnly(com.ultikits.ultitools.manager.UltiPanelLogTransmitter)) across all THREE paths that can trigger one:sendBatchUpdate()'s 5-second tick,maybeSendLogsOnly()'s 1-second tick, anddrainLogsNow()'s size-triggered, event-driven call (Gate-2 finding, round 7).claimLogFlushWindow(long, int)'s CAS only arbitrates between the two TIME-based paths against each other;drainLogsNow()deliberately does not participate in that CAS at all (it is not time-gated), so without a separate mutual-exclusion lock it could run concurrently with whichever time-based path just won the CAS, and the two would poll() the same queue at once, splitting one logical batch across two frames with no ordering guarantee between them. This lock is orthogonal to the CAS: the CAS decides WHETHER a time-based path may proceed at all; this lock decides that only ONE drain (whichever path triggered it) is ever in flight at a time. -
diskUsageRoot
The file whose filesystem backs thediskUsagemetric -- the server root (the working directory the Paper process was started in), matchingFileOperationManager's ownserverRootconvention. Package-private and mutable only so tests can point it at a controlled location; production code never reassigns it (D-14, #436). -
totalSpaceReader
Seam overFile.getTotalSpace(), so "the filesystem reports zero total space" (D-14) is deterministic in tests without needing an actual zero-capacity filesystem. Defaults to the real method reference. -
freeSpaceReader
Seam overFile.getFreeSpace()(WR-03) -- includes space reserved for the filesystem's own root/superuser allocation, matching whatdf's ownUse%column treats as "used" (total minus this value). Distinct fromusableSpaceReader(File.getUsableSpace()), which EXCLUDES that reservation -- the two diverge on a real ext4 volume with a nonzero reserved-block percentage. -
usableSpaceReader
Seam overFile.getUsableSpace(), same rationale astotalSpaceReader.
-
-
Constructor Details
-
ServerMonitorManager
public ServerMonitorManager()
-
-
Method Details
-
getLogDrainLock
Object getLogDrainLock()ExposeslogDrainLocksoLogStreamManager.initialize(com.ultikits.ultitools.websocket.UltiPanelWebSocketClient)can wireUltiPanelLogTransmitter.setExternalDrainCoordinationLock(java.lang.Object)to the SAME lock object this class's own drain paths already synchronize on (Gate-2 finding, round 9). Package-private -- this is internal cross-class coordination, not a public API. -
claimLogFlushWindow
private boolean claimLogFlushWindow(long now, int intervalMs) Atomically claims the current log-flush window if (and only if)intervalMshas elapsed since the last successful claim, advancinglastLogFlushMstonowas part of the same compare-and-set. Returnsfalsewithout side effects if the interval has not elapsed, OR if a concurrently-running competing task already claimed this exact window first (Gate-2 finding, round 4 -- seelastLogFlushMs's own javadoc).- Parameters:
now- the caller's ownSystem.currentTimeMillis()snapshotintervalMs- the currently configured batch-send interval- Returns:
trueonly for the caller that should proceed to drain and send
-
setDiskUsageRoot
Test seam: point the disk-usage reading at a different root (e.g. a real temp directory). Production code never calls this. -
setDiskSpaceReaders
void setDiskSpaceReaders(ToLongFunction<File> totalSpaceReader, ToLongFunction<File> freeSpaceReader, ToLongFunction<File> usableSpaceReader) Test seam: inject fake total/free/usable-space readers (e.g. to force the zero-total-space outcome deterministically, or to pin a known used-percentage without depending on the real filesystem's actual free space). Production code never calls this. -
setWebSocketClient
@Deprecated(since="6.3.1", forRemoval=true) public void setWebSocketClient(UltiPanelWebSocketClient client) Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework never calls it, and its own UltiCloud session (protocol 2.0.0) carries the panel requests. Removed in 6.4.0.Set the WebSocket client.- Parameters:
client- the WebSocket client- Scheduled removal version:
- 6.4.0
-
startMonitoring
Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself. Removed in 6.4.0.Start monitoring server status: the protocol v1batch_updatesender, its log drain and the two sampling tasks it owns. As of 6.3.1 the framework never calls it; the protocol 2.0.0 session samples through its own telemetry instead.- Scheduled removal version:
- 6.4.0
-
stopMonitoring
Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself. Removed in 6.4.0.Stop monitoring.Besides shutting down the send thread, the two main-thread scheduled tasks must also be cancelled. This used to be just
scheduler.shutdown(), which was enough before sampling moved to the main thread -- the cost of iterating every world lived on the send thread, so shutting down sending removed it too. After the move, not cancelling turns "stop monitoring" into "stop sending, but still iterate every world and chunk every 5 seconds" -- and now on the main thread. See the PR #265 review.- Scheduled removal version:
- 6.4.0
-
cancelTask
private static void cancelTask(org.bukkit.scheduler.BukkitTask task) -
sendServerStatus
Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself. Removed in 6.4.0.Send server status information.- Scheduled removal version:
- 6.4.0
-
extractVersionNumber
Extract the version number. -
getCPUUsage
private double getCPUUsage()Get the CPU usage (returns the cached value, updated by periodic sampling). -
sampleCpuUsage
private void sampleCpuUsage()Sample CPU usage and update the cached value. -
sendServerStatusWithRequestId
@Deprecated(since="6.3.1", forRemoval=true) public void sendServerStatusWithRequestId(String requestId) Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself. Removed in 6.4.0.Send server status information with a request ID (in response to a backend request).- Scheduled removal version:
- 6.4.0
-
sampleServerState
Samples a server-state snapshot on the main thread.May only be called on the Bukkit main thread. A call from a non-main thread is refused and logged as SEVERE -- this is a second, defensive gate: scheduling already guarantees the thread, but this class's own history is exactly "the contract was recognized, only half applied", and writing the contract into the code is more reliable than writing it into a comment.
- Returns:
- the new snapshot;
nullif not on the main thread
-
isOnPrimaryThreadOrComplain
private boolean isOnPrimaryThreadOrComplain()The single check point for the thread contract: logs SEVERE and returns false when not on the main thread.This class's own history is exactly "the contract was recognized, only half applied", so the contract is written as a runtime-observable signal rather than trusted to a comment -- on a real server, this line appearing in the log is proof that scheduling was broken.
-
refreshStateSnapshot
void refreshStateSnapshot()Entry point for the main-thread scheduled task: samples and publishes the snapshot.Package-visible so tests can drive it directly.
-
sampleAndPublishState
boolean sampleAndPublishState()Samples and publishes one state snapshot without the v1 connection gaterefreshStateSnapshot()applies, so the v2 telemetry path and its tests can publish a fresh sample while no v1 socket exists.May only be called on the Bukkit main thread;
sampleServerState()refuses any other thread and logs it as SEVERE.- Returns:
truewhen a fresh snapshot was published,falseoff the main thread or when sampling failed
-
sampleTpsAndCpu
boolean sampleTpsAndCpu()Runs one TPS and CPU sample on the main thread, without starting the v1 sender, the log drain or either sampling taskstartMonitoring()owns.The v2 telemetry session (plan 22-22) schedules this once per second in place of the v1
tpsTask:getCurrentTPS()andsampledCpuPercent()read what it records. It deliberately does not callstartMonitoring(), which would also start the v1batch_updatesender and the log drain.May only be called on the Bukkit main thread, like every other sample this class takes; any other thread is refused and logged as SEVERE.
- Returns:
truewhen the sample ran,falseoff the main thread
-
primeTpsClock
boolean primeTpsClock()Starts the TPS clock now without recording a sample (plan 22-22 fix): the clock otherwise starts when this object is created, and the v2 sampler first runs 20 ticks after the first connection, which may come long after the plugin enabled; that gap would be recorded as the first TPS sample. CPU is sampled once so the next sample has a value. Main thread only, likesampleTpsAndCpu().- Returns:
truewhen the clock was started,falseoff the main thread
-
currentStateSnapshot
ServerMonitorManager.ServerStateSnapshot currentStateSnapshot()Reads the published main-thread sample for the v2 telemetry encoders (AgentTelemetry). Package-private: internal cross-class reading, not a public API, and the v1 send paths keep usingcurrentSnapshot().Samples once here when nothing has been published yet and the caller is on the main thread, exactly like
currentSnapshot(); otherwise it returns the last publication, which may be up to one sampling period old.- Returns:
- the latest sampled state, or the empty snapshot before the first sample
-
sampledCpuPercent
double sampledCpuPercent()- Returns:
- the process CPU load percentage measured by the 1 Hz main-thread task, unclamped (the contract's encoder applies its own bounds)
-
hasSampledState
boolean hasSampledState()Lets tests assert whether sampling has actually happened yet -- more direct than observing the emitted JSON externally. -
currentSnapshot
Gets the current snapshot for the send thread to use.If sampling has never happened even once and the caller happens to be on the main thread, sample once right here -- otherwise the first frame after the connection is established would be all zeroes, and on-demand requests like
sendServerStatusWithRequestIdwould return empty data before monitoring has started. -
getCurrentServerStatusData
private com.google.gson.JsonObject getCurrentServerStatusData()Gets the current server status data.Every Bukkit-derived field comes from the main-thread-sampled snapshot (see
stateSnapshot); this method itself may be called from any thread. Runtime memory, JVM uptime, TPS and CPU are not Bukkit state and are read in place -- TPS and CPU are already maintained by the main thread's 1Hz task. -
sendBatchUpdate
private void sendBatchUpdate()Sends the batch_update message, merging status, metrics, plugins and logs into a single WebSocket frame. Called every 5 seconds; plugins is included once every 12th tick (60 seconds). -
maybeSendLogsOnly
private void maybeSendLogsOnly()Checks the log-drain interval gate at aLOG_FLUSH_POLL_INTERVAL_MSgranularity (narrowed from 1 second in round 11), independent ofsendBatchUpdate()'s own fixed 5-second tick (Gate-2 finding, round 3).Both this method and
sendBatchUpdate()callclaimLogFlushWindow(long, int)against the SAMElastLogFlushMsfield, so only one of the two competing scheduled tasks ever drains for a given window (Gate-2 finding, round 4 -- seelastLogFlushMs's own javadoc for why a plainvolatile longcheck-then-write was not race-safe here).Sends a
batch_updatemessage carrying ONLYdata.logswhen logs are both due and non-empty -- not a new message type: the Worker's own handler (`websocket-server.ts` `case 'batch_update'`) already guards every field withif (batch.X), so a partial batch_update is an already-supported shape, not a protocol change. -
drainLogsNow
void drainLogsNow()Immediately drains and sends whatever is currently queued, without waiting for eithersendBatchUpdate()'s 5-second tick ormaybeSendLogsOnly()'s 1-second tick (Gate-2 finding, round 6). Registered asUltiPanelLogTransmitter'sexternalSizeThresholdCallbackfromLogStreamManager.initialize(com.ultikits.ultitools.websocket.UltiPanelWebSocketClient): under external drain mode,addToBatch()'s own size-threshold trigger is otherwise silently swallowed bysendBatch()'s own early return, so a configuredbatchSizenever shortened delivery latency while monitoring was active, and a sustained burst could fill the transmitter's queue cap and start discarding old entries despite repeatedly crossing the threshold.Deliberately does NOT go through
claimLogFlushWindow(long, int)'s interval gate: that gate rate-limits the two TIME-based tasks against the configured interval, but this trigger is queue-DEPTH-based, a different reason to drain, and the whole point is to bypass waiting for the interval when the queue is already at capacity risk.drainQueueis safe to call concurrently with the other two tasks (ConcurrentLinkedQueue#pollis atomic per call) -- a near-simultaneous interval-triggered drain and this size-triggered one would, at worst, split one burst across two frames rather than lose or duplicate any entry.lastLogFlushMsis still advanced afterwards so the interval-based tasks do not immediately re-fire for the records this call already sent. -
drainAndSendLogsOnly
Drains up totransmitter's configuredbatchSizeand, if non-empty, sends abatch_updatemessage carrying ONLYdata.logs-- shared bymaybeSendLogsOnly()anddrainLogsNow()(Gate-2 finding, round 6 extraction). Not a new message type: the Worker's own handler (`websocket-server.ts` `case 'batch_update'`) already guards every field withif (batch.X), so a partial batch_update is an already-supported shape, not a protocol change. Callers are responsible for their own connectivity/capability checks and for advancinglastLogFlushMs.Holds
logDrainLockfor its whole body (Gate-2 finding, round 7): the poll-and-send sequence itself must be mutually exclusive across all three callers, not just gated byclaimLogFlushWindow(long, int)'s CAS (which only arbitrates the two TIME-based callers against each other and is not even consulted bydrainLogsNow()). -
sendKeepingUndeliveredLogs
private void sendKeepingUndeliveredLogs(com.google.gson.JsonObject message, UltiPanelLogTransmitter transmitter, com.google.gson.JsonArray logs) Sends abatch_updateframe and, when it carried log records and did not go out, hands them back to the transmitter so the next drain sends them before anything newer (#486). The frame did not go out ifsendMessagethrew -- the exception still propagates to the caller's own handler -- or if the client no longer reports itself connected afterwards, which is what the closed-socket path ofsendMessageleaves behind. Called withlogDrainLockheld, so the hand-back cannot interleave with another drain. -
getCurrentPluginArray
private com.google.gson.JsonArray getCurrentPluginArray()Gets the current plugin list array.Taken from the main-thread-sampled snapshot.
Bukkit.getPluginManager().getPlugins()likewise should not be iterated from an async thread. -
getCurrentMetricsData
private com.google.gson.JsonObject getCurrentMetricsData()Gets the current performance metrics data.Bukkit-derived fields come from the snapshot; Runtime memory and TPS are computed in place.
-
computeDiskUsage
private double computeDiskUsage()The used percentage of the filesystem holdingdiskUsageRoot, matchingdf's ownUse%convention (WR-03) rather than a straight(total - usable) / total:used = total - free(viafreeSpaceReader,File.getFreeSpace()-- INCLUDES space reserved for the filesystem's own root/superuser allocation), and the percentage isused / (used + avail)withavailfromusableSpaceReader(File.getUsableSpace()-- EXCLUDES that reservation). The earlier(total - usable) / totalformula measured roughly one percentage point higher thandfon a real ext4 volume with its default ~5% reserved-block allocation, at or past this metric's own documented one-point UAT tolerance -- see this plan's gate record for the real-machine measurement. Rounded to two decimals exactly likememoryUsageabove (D-14, #436). A filesystem reporting zero total space, or a used+avail denominator of zero, is a stated outcome --0.0-- not a division error. -
countEnabledPlugins
static int countEnabledPlugins(com.google.gson.JsonArray plugins) Counts entries inpluginswhoseenabledfield istrue-- the per-plugin flagsampleServerState()already captures for every installed plugin (D-14, #437:enabledPluginsmust count plugins that are enabled, not every plugin installed). Package-private and pure so it is unit-testable directly against a syntheticJsonArraywithout needing a multi-plugin MockBukkit fixture.- Parameters:
plugins- the snapshot's plugin array; each element is expected to be aJsonObjectcarrying a booleanenabledfield- Returns:
- the number of entries whose
enabledfield istrue;0for an empty array
-
sendMetricsData
Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself. Removed in 6.4.0.Sends performance metrics data (standalone message, for on-demand requests).- Scheduled removal version:
- 6.4.0
-
sendMetricsDataWithRequestId
@Deprecated(since="6.3.1", forRemoval=true) public void sendMetricsDataWithRequestId(String requestId) Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself. Removed in 6.4.0.Sends performance metrics data with a request ID (in response to a backend request).- Scheduled removal version:
- 6.4.0
-
updateTpsAndCpu
private void updateTpsAndCpu()Updates the TPS calculation + CPU sampling -- runs once per second (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()Calculates TPS -- returns the [1-minute, 5-minute, 15-minute] averages. -
calculateAverageTPS
private double calculateAverageTPS(long[] history, int count) Calculates the average of a history-of-TPS array. -
calculateRealtimeTPS
private double calculateRealtimeTPS()Calculates the realtime TPS (based on the most recent history entry). -
sendPlayerEvent
@Deprecated(since="6.3.1", forRemoval=true) public void sendPlayerEvent(String eventType, org.bukkit.entity.Player player, com.google.gson.JsonObject additionalData) Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) reports player events itself. Removed in 6.4.0.Sends a player event.- Scheduled removal version:
- 6.4.0
-
isMonitoring
Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework's own UltiCloud session (protocol 2.0.0) sends the server's status and telemetry itself. Removed in 6.4.0.Whether the protocol v1 monitor that startMonitoring() starts is running.- Scheduled removal version:
- 6.4.0
-