Class AgentTelemetry

java.lang.Object
com.ultikits.ultitools.manager.AgentTelemetry
All Implemented Interfaces:
CloudRequestHandler

final class AgentTelemetry extends Object implements CloudRequestHandler
Encodes and paces the UltiCloud protocol v2 telemetry frames; internal, not for module use.

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
  • Field Details

    • LOGGER

      private static final Logger LOGGER
    • STATUS_INTERVAL_MS

      static final long STATUS_INTERVAL_MS
      Contract timing.statusIntervalMs; status is sent once per interval.
      See Also:
    • SNAPSHOT_MIN_INTERVAL_MS

      static final long SNAPSHOT_MIN_INTERVAL_MS
      Contract timing.playersSnapshotMinIntervalMs; also the coalescing window of the other snapshots.
      See Also:
    • MAX_PLAYERS_COUNT

      private static final int MAX_PLAYERS_COUNT
      Status payload bound from the frozen status schema.
      See Also:
    • MAX_MEMORY_MB

      private static final long MAX_MEMORY_MB
      Status payload bound from the frozen status schema.
      See Also:
    • MAX_UPTIME_SECONDS

      private static final long MAX_UPTIME_SECONDS
      Status payload bound from the frozen status schema.
      See Also:
    • MAX_WORLD_COUNT

      private static final int MAX_WORLD_COUNT
      Status payload bound from the frozen status schema.
      See Also:
    • MAX_PLAYER_ENTRIES

      private static final int MAX_PLAYER_ENTRIES
      Snapshot array bounds from the frozen players.snapshot schema.
      See Also:
    • MAX_PLUGIN_ENTRIES

      private static final int MAX_PLUGIN_ENTRIES
      Snapshot array bounds from the frozen plugins.snapshot schema.
      See Also:
    • MAX_WORLD_ENTRIES

      private static final int MAX_WORLD_ENTRIES
      Snapshot array bounds from the frozen worlds.snapshot schema.
      See Also:
    • MAX_LOADED_CHUNKS

      private static final int MAX_LOADED_CHUNKS
      World payload bound from the frozen worlds.snapshot schema.
      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

      private static final String STATUS
      See Also:
    • PLAYERS_SNAPSHOT

      private static final String PLAYERS_SNAPSHOT
      See Also:
    • PLUGINS_SNAPSHOT

      private static final String PLUGINS_SNAPSHOT
      See Also:
    • WORLDS_SNAPSHOT

      private static final String WORLDS_SNAPSHOT
      See Also:
    • TARGETS

      static final String[] TARGETS
      The refresh targets snapshot.request may name, in schema order (SEMANTICS item 4).
    • SNAPSHOT_REQUEST_RESULT

      private static final String SNAPSHOT_REQUEST_RESULT
      See Also:
    • ENVIRONMENTS

      private static final String[] ENVIRONMENTS
    • DIFFICULTIES

      private static final String[] DIFFICULTIES
    • GAME_MODES

      private static final String[] GAME_MODES
    • EMBEDDED_SEMVER

      private static final Pattern EMBEDDED_SEMVER
      The one field a version string must contain to satisfy the contract's Semver pattern.
    • UNKNOWN_VERSION

      private static final String UNKNOWN_VERSION
      Used when neither the plugin description nor env.yml carries a usable version.
      See Also:
    • UNKNOWN_SOFTWARE

      private static final String UNKNOWN_SOFTWARE
      Used when the server reports neither a name nor a version.
      See Also:
    • UNKNOWN_PLUGIN_VERSION

      private static final String UNKNOWN_PLUGIN_VERSION
      Used for a plugin whose manifest carries no version; the schema requires a non-empty string.
      See Also:
    • monitor

      private final ServerMonitorManager monitor
    • telemetryEnabled

      private final BooleanSupplier telemetryEnabled
    • clock

      private final LongSupplier clock
    • mainThread

      private final Executor mainThread
    • statusTrack

      private final AgentTelemetry.Tracked statusTrack
    • playerTrack

      private final AgentTelemetry.Tracked playerTrack
    • pluginTrack

      private final AgentTelemetry.Tracked pluginTrack
    • worldTrack

      private final AgentTelemetry.Tracked 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 load
      telemetryEnabled - the telemetry capability switch; false silences every frame
      clock - supplies epoch milliseconds for the cadence, never the wall clock directly
      mainThread - runs the sampling and the reply of a snapshot.request: Bukkit state may only be read there, while the router dispatches on its own threads
  • Method Details

    • onWelcome

      void onWelcome(CloudChannel channel)
      Sends the four snapshots a fresh connection starts from: status, players.snapshot, plugins.snapshot and worlds.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.Tracked is 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 race tick(CloudChannel).

      Parameters:
      channel - the accepted connection
    • sendWelcome

      private void sendWelcome(CloudChannel channel)
    • 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

      void tick(CloudChannel channel)
      Advances the cadence; the session wiring calls this about once a second.

      Thread rule: the Bukkit main thread only. Unlike onWelcome(CloudChannel) and onClose(), 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

      public void handle(CloudRequest request, CloudChannel channel)
      Handles snapshot.request: pushes the requested snapshots in request order and then answers snapshot.request.result with 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:
      handle in interface CloudRequestHandler
      Parameters:
      request - the parsed request
      channel - the reply channel
    • requestedTargets

      private static List<String> requestedTargets(CloudRequest request)
      Returns:
      the requested targets with duplicates collapsed, which the schema cannot express and SEMANTICS item 9 makes the plugin's job
    • snapshotTarget

      private static String snapshotTarget(String id, com.google.gson.JsonElement element)
      Validates one target through the shared payload reader, so an unknown or misspelled element fails as invalid_message rather than being dropped silently.
    • sendRequested

      private void sendRequested(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.

      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

      private Supplier<com.google.gson.JsonObject> payloadSupplier(String type)
    • track

      private AgentTelemetry.Tracked track(String type)
    • 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

      private boolean enabled(CloudChannel channel)
    • send

      private static boolean send(CloudChannel channel, String type, com.google.gson.JsonObject payload)
    • statusPayload

      private com.google.gson.JsonObject statusPayload()
      Builds the contract status payload: 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()
      Builds players.snapshot from 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()
      Builds plugins.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()
      Builds worlds.snapshot: name, environment, difficulty, player count, loaded chunks, PvP and the spawn point. An environment or difficulty the contract does not name maps to custom or normal respectively, as the plan requires.
    • worldEntry

      private static com.google.gson.JsonObject worldEntry(com.google.gson.JsonElement element)
    • omitEntity

      private static com.google.gson.JsonObject omitEntity(String reason)
    • software

      private static String software(String serverVersion)
      Returns:
      the server name and version, cut to the contract's 128 code points
    • ultitoolsVersion

      private static String 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, else 0.0.0 -- a frame is never allowed to carry a version the schema rejects
    • semver

      private static String semver(String versionOrNull)
    • orDefault

      private static String orDefault(String value, String fallback)
    • text

      private static String text(com.google.gson.JsonObject source, String key)
    • bool

      private static boolean bool(com.google.gson.JsonObject source, String key)
    • number

      private static double number(com.google.gson.JsonObject source, String key)
    • integer

      private static int integer(com.google.gson.JsonObject source, String key)
    • spawnCoordinate

      private static int spawnCoordinate(com.google.gson.JsonElement spawn, String axis)
    • lowerCase

      private static String lowerCase(String value)
    • contains

      private static boolean contains(String[] values, String value)
    • nonEmpty

      private static String nonEmpty(String value)
    • codePoints

      private static int codePoints(String value)
    • cutCodePoints

      private static String cutCodePoints(String value, int max)
      Cuts a string to max Unicode 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)