Class AgentTelemetry
- All Implemented Interfaces:
CloudRequestHandler
The class is passive: the session wiring (plan 22-22) calls onWelcome(CloudChannel)
when a connection is accepted, tick(CloudChannel) roughly once a second, and
onClose() when the connection ends. All timing goes through the injected clock, so the
cadence is testable without a socket and without sleeping. onWelcome and onClose
may be called from any thread and hand their work to the injected main-thread executor;
tick must already run on the main thread.
Cadence follows the frozen contract's SEMANTICS item 6: status every
10000L ms; players.snapshot, plugins.snapshot and
worlds.snapshot once after the welcome and afterwards only when their mapped payload
changed, at most once per 10000L ms each. Change detection compares
what would be sent, so a change in a field the contract does not carry never triggers a frame.
Every value is mapped into the contract's field set and bounds, and no field the contract
does not define is sent (no addresses, no plugin authors). Bukkit state is read only through
ServerMonitorManager's main-thread sample.
- Since:
- 6.3.1
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classThe last successful send of one snapshot type: how recently, and what went on the wire. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final LongSupplierprivate static final String[]private static final PatternThe one field a version string must contain to satisfy the contract's Semver pattern.private static final String[]private static final String[]private static final Loggerprivate final Executorprivate static final intprivate static final doubleprivate static final intWorld payload bound from the frozenworlds.snapshotschema.private static final longStatus payload bound from the frozenstatusschema.private static final intSnapshot array bounds from the frozenplayers.snapshotschema.private static final intStatus payload bound from the frozenstatusschema.private static final intSnapshot array bounds from the frozenplugins.snapshotschema.private static final intprivate static final intprivate static final longStatus payload bound from the frozenstatusschema.private static final intprivate static final intprivate static final intStatus payload bound from the frozenstatusschema.private static final intSnapshot array bounds from the frozenworlds.snapshotschema.private static final intprivate final ServerMonitorManagerprivate static final Stringprivate final AgentTelemetry.Trackedprivate static final Stringprivate final AgentTelemetry.Tracked(package private) static final longContracttiming.playersSnapshotMinIntervalMs; also the coalescing window of the other snapshots.private static final Stringprivate static final String(package private) static final longContracttiming.statusIntervalMs;statusis sent once per interval.private final AgentTelemetry.Tracked(package private) static final String[]The refresh targetssnapshot.requestmay name, in schema order (SEMANTICS item 4).private final BooleanSupplierprivate static final StringUsed for a plugin whose manifest carries no version; the schema requires a non-empty string.private static final StringUsed when the server reports neither a name nor a version.private static final StringUsed when neither the plugin description norenv.ymlcarries a usable version.private static final Stringprivate final AgentTelemetry.Tracked -
Constructor Summary
ConstructorsConstructorDescriptionAgentTelemetry(ServerMonitorManager monitor, BooleanSupplier telemetryEnabled, LongSupplier clock, Executor mainThread) Creates the encoder over the monitor's main-thread sample. -
Method Summary
Modifier and TypeMethodDescriptionprivate static booleanprivate static doubleclamp(double value, double min, double max) private static intclampInt(int value, int min, int max) private static longclampLong(long value, long min, long max) private static intcodePoints(String value) private static booleanprivate static StringcutCodePoints(String value, int max) Cuts a string tomaxUnicode code points, never splitting a surrogate pair.private booleanenabled(CloudChannel channel) private voidvoidhandle(CloudRequest request, CloudChannel channel) Handlessnapshot.request: pushes the requested snapshots in request order and then answerssnapshot.request.resultwith what was sent (SEMANTICS item 4, checker B-W3).private static intprivate static Stringprivate static Stringprivate static doubleprivate static com.google.gson.JsonObjectomitEntity(String reason) (package private) voidonClose()Forgets what was last sent, so the next welcome sends every snapshot again.(package private) voidonWelcome(CloudChannel channel) Sends the four snapshots a fresh connection starts from:status,players.snapshot,plugins.snapshotandworlds.snapshot, in that order.private static Stringprivate Supplier<com.google.gson.JsonObject> payloadSupplier(String type) private static com.google.gson.JsonObjectplayerEntry(com.google.gson.JsonElement element) private com.google.gson.JsonObjectBuildsplayers.snapshotfrom the sampled player array, keeping only the contract's seven fields.private com.google.gson.JsonObjectBuildsplugins.snapshot: name, version, description (explicit JSON null when the manifest has none) and enabled.requestedTargets(CloudRequest request) private static doubleround(double value, int decimals) private static Stringprivate static booleansend(CloudChannel channel, String type, com.google.gson.JsonObject payload) private static voidsendChanged(CloudChannel channel, String type, Supplier<com.google.gson.JsonObject> payload, AgentTelemetry.Tracked track, long now) Sends one snapshot when its mapped payload differs from the last one sent and at least 10000L ms have passed since that send.private static booleansendForced(CloudChannel channel, String type, Supplier<com.google.gson.JsonObject> payload, AgentTelemetry.Tracked track, long now) Sends one snapshot regardless of the coalescing window, because the panel asked for it, and restarts that snapshot's window from now.private voidsendRequested(String replyTo, List<String> targets, CloudChannel channel) Takes a fresh main-thread sample, then pushes the requested snapshots whether or not their content changed -- the panel asked for them -- and restarts each one's coalescing window from now, then answers the request.private voidsendWelcome(CloudChannel channel) private static StringsnapshotTarget(String id, com.google.gson.JsonElement element) Validates one target through the shared payload reader, so an unknown or misspelled element fails asinvalid_messagerather than being dropped silently.private static Stringprivate static intspawnCoordinate(com.google.gson.JsonElement spawn, String axis) private com.google.gson.JsonObjectBuilds the contractstatuspayload: exactly its ten fields, every value inside the schema bounds.private static String(package private) voidtick(CloudChannel channel) Advances the cadence; the session wiring calls this about once a second.private AgentTelemetry.Trackedprivate static Stringprivate static com.google.gson.JsonObjectworldEntry(com.google.gson.JsonElement element) private com.google.gson.JsonObjectBuildsworlds.snapshot: name, environment, difficulty, player count, loaded chunks, PvP and the spawn point.
-
Field Details
-
LOGGER
-
STATUS_INTERVAL_MS
static final long STATUS_INTERVAL_MSContracttiming.statusIntervalMs;statusis sent once per interval.- See Also:
-
SNAPSHOT_MIN_INTERVAL_MS
static final long SNAPSHOT_MIN_INTERVAL_MSContracttiming.playersSnapshotMinIntervalMs; also the coalescing window of the other snapshots.- See Also:
-
MAX_PLAYERS_COUNT
private static final int MAX_PLAYERS_COUNTStatus payload bound from the frozenstatusschema.- See Also:
-
MAX_MEMORY_MB
private static final long MAX_MEMORY_MBStatus payload bound from the frozenstatusschema.- See Also:
-
MAX_UPTIME_SECONDS
private static final long MAX_UPTIME_SECONDSStatus payload bound from the frozenstatusschema.- See Also:
-
MAX_WORLD_COUNT
private static final int MAX_WORLD_COUNTStatus payload bound from the frozenstatusschema.- See Also:
-
MAX_PLAYER_ENTRIES
private static final int MAX_PLAYER_ENTRIESSnapshot array bounds from the frozenplayers.snapshotschema.- See Also:
-
MAX_PLUGIN_ENTRIES
private static final int MAX_PLUGIN_ENTRIESSnapshot array bounds from the frozenplugins.snapshotschema.- See Also:
-
MAX_WORLD_ENTRIES
private static final int MAX_WORLD_ENTRIESSnapshot array bounds from the frozenworlds.snapshotschema.- See Also:
-
MAX_LOADED_CHUNKS
private static final int MAX_LOADED_CHUNKSWorld payload bound from the frozenworlds.snapshotschema.- See Also:
-
MAX_SOFTWARE_CODE_POINTS
private static final int MAX_SOFTWARE_CODE_POINTS- See Also:
-
MAX_VERSION_CODE_POINTS
private static final int MAX_VERSION_CODE_POINTS- See Also:
-
MAX_PLUGIN_NAME_CODE_POINTS
private static final int MAX_PLUGIN_NAME_CODE_POINTS- See Also:
-
MAX_DESCRIPTION_CODE_POINTS
private static final int MAX_DESCRIPTION_CODE_POINTS- See Also:
-
MAX_WORLD_NAME_CODE_POINTS
private static final int MAX_WORLD_NAME_CODE_POINTS- See Also:
-
MAX_HEALTH
private static final double MAX_HEALTH- See Also:
-
MAX_WORLD_COORDINATE
private static final int MAX_WORLD_COORDINATE- See Also:
-
STATUS
- See Also:
-
PLAYERS_SNAPSHOT
- See Also:
-
PLUGINS_SNAPSHOT
- See Also:
-
WORLDS_SNAPSHOT
- See Also:
-
TARGETS
The refresh targetssnapshot.requestmay name, in schema order (SEMANTICS item 4). -
SNAPSHOT_REQUEST_RESULT
- See Also:
-
ENVIRONMENTS
-
DIFFICULTIES
-
GAME_MODES
-
EMBEDDED_SEMVER
The one field a version string must contain to satisfy the contract's Semver pattern. -
UNKNOWN_VERSION
Used when neither the plugin description norenv.ymlcarries a usable version.- See Also:
-
UNKNOWN_SOFTWARE
Used when the server reports neither a name nor a version.- See Also:
-
UNKNOWN_PLUGIN_VERSION
Used for a plugin whose manifest carries no version; the schema requires a non-empty string.- See Also:
-
monitor
-
telemetryEnabled
-
clock
-
mainThread
-
statusTrack
-
playerTrack
-
pluginTrack
-
worldTrack
-
-
Constructor Details
-
AgentTelemetry
AgentTelemetry(ServerMonitorManager monitor, BooleanSupplier telemetryEnabled, LongSupplier clock, Executor mainThread) Creates the encoder over the monitor's main-thread sample.- Parameters:
monitor- supplies the sampled server state, TPS and CPU loadtelemetryEnabled- thetelemetrycapability switch; false silences every frameclock- supplies epoch milliseconds for the cadence, never the wall clock directlymainThread- runs the sampling and the reply of asnapshot.request: Bukkit state may only be read there, while the router dispatches on its own threads
-
-
Method Details
-
onWelcome
Sends the four snapshots a fresh connection starts from:status,players.snapshot,plugins.snapshotandworlds.snapshot, in that order. Nothing is sent while the telemetry capability is off or the channel is closed.Thread rule: a welcome arrives on the WebSocket thread, but the sample and the cadence bookkeeping belong to the Bukkit main thread, so the work is handed to the injected main-thread executor and every
AgentTelemetry.Trackedis only touched there. Callers may invoke this from any thread; a welcome that ran on the socket thread would send the empty sample (before the first main-thread sample) and racetick(CloudChannel).- Parameters:
channel- the accepted connection
-
sendWelcome
-
onClose
void onClose()Forgets what was last sent, so the next welcome sends every snapshot again. The next connection may well be a different gateway with a different view.Thread rule: the bookkeeping is only touched on the Bukkit main thread, so the clear is handed to the injected main-thread executor, exactly like
onWelcome(CloudChannel). Callers may invoke this from any thread (a close arrives on the WebSocket thread); going through the same executor keeps the clear ordered after any welcome still queued ahead of it. -
forgetSentState
private void forgetSentState() -
tick
Advances the cadence; the session wiring calls this about once a second.Thread rule: the Bukkit main thread only. Unlike
onWelcome(CloudChannel)andonClose(), which defer their work here, this method mutates the bookkeeping directly and therefore must already be on that thread.- Parameters:
channel- the accepted connection
-
sendForced
private static boolean sendForced(CloudChannel channel, String type, Supplier<com.google.gson.JsonObject> payload, AgentTelemetry.Tracked track, long now) Sends one snapshot regardless of the coalescing window, because the panel asked for it, and restarts that snapshot's window from now. -
handle
Handlessnapshot.request: pushes the requested snapshots in request order and then answerssnapshot.request.resultwith what was sent (SEMANTICS item 4, checker B-W3).The target list is validated here, on the dispatching thread, so a payload the schema rejects fails exactly like any other invalid message. The push itself, including the fresh sample it takes before encoding, runs on the injected main-thread executor, because sampling reads Bukkit state.
- Specified by:
handlein interfaceCloudRequestHandler- Parameters:
request- the parsed requestchannel- the reply channel
-
requestedTargets
- Returns:
- the requested targets with duplicates collapsed, which the schema cannot express and SEMANTICS item 9 makes the plugin's job
-
snapshotTarget
Validates one target through the shared payload reader, so an unknown or misspelled element fails asinvalid_messagerather than being dropped silently. -
sendRequested
Takes a fresh main-thread sample, then pushes the requested snapshots whether or not their content changed -- the panel asked for them -- and restarts each one's coalescing window from now, then answers the request.The sample is what makes a refresh useful: the encoders read the last published sample, which may be up to one sampling period old, so without this a refresh would resend stale values. A sample that fails (off the main thread, or a sampling error) leaves the last published state in place and the request is still answered.
-
payloadSupplier
-
track
-
sendChanged
private static void sendChanged(CloudChannel channel, String type, Supplier<com.google.gson.JsonObject> payload, AgentTelemetry.Tracked track, long now) Sends one snapshot when its mapped payload differs from the last one sent and at least 10000L ms have passed since that send. Comparison is on the payload that would go on the wire, so a value the contract does not carry cannot wake it. -
enabled
-
send
-
statusPayload
private com.google.gson.JsonObject statusPayload()Builds the contractstatuspayload: exactly its ten fields, every value inside the schema bounds. Bukkit state comes from the monitor's sample; memory, uptime, TPS and CPU are JVM readings taken here (all four are safe off the main thread). -
playersPayload
private com.google.gson.JsonObject playersPayload()Buildsplayers.snapshotfrom the sampled player array, keeping only the contract's seven fields. A player whose name, world or game mode cannot be represented is omitted and logged at FINE; the name is never logged. -
playerEntry
private static com.google.gson.JsonObject playerEntry(com.google.gson.JsonElement element) -
pluginsPayload
private com.google.gson.JsonObject pluginsPayload()Buildsplugins.snapshot: name, version, description (explicit JSON null when the manifest has none) and enabled. Plugin authors are deliberately not carried over. -
worldsPayload
private com.google.gson.JsonObject worldsPayload()Buildsworlds.snapshot: name, environment, difficulty, player count, loaded chunks, PvP and the spawn point. An environment or difficulty the contract does not name maps tocustomornormalrespectively, as the plan requires. -
worldEntry
private static com.google.gson.JsonObject worldEntry(com.google.gson.JsonElement element) -
omitEntity
-
software
- Returns:
- the server name and version, cut to the contract's 128 code points
-
ultitoolsVersion
- Returns:
- the running framework version in the contract's Semver form: the plugin
description's version when it already is Semver, else the first Semver-looking run
inside it, else
env.yml's, else0.0.0-- a frame is never allowed to carry a version the schema rejects
-
semver
-
orDefault
-
text
-
bool
-
number
-
integer
-
spawnCoordinate
-
lowerCase
-
contains
-
nonEmpty
-
codePoints
-
cutCodePoints
Cuts a string tomaxUnicode code points, never splitting a surrogate pair. -
clamp
private static double clamp(double value, double min, double max) -
clampLong
private static long clampLong(long value, long min, long max) -
clampInt
private static int clampInt(int value, int min, int max) -
round
private static double round(double value, int decimals)
-