From 264889719750bd92bd4d4d04ee93d985da42c1b7 Mon Sep 17 00:00:00 2001 From: Dylan Garvis Date: Thu, 22 Jan 2026 23:47:47 -0500 Subject: [PATCH] fix: snapshot entity metrics on main thread --- README.md | 3 +- .../spigot/PrometheusSpigotPlugin.java | 17 ++++++ .../spigot/metrics/MetricsRegistry.java | 61 +++++++++++-------- src/main/resources/config.yml | 1 + 4 files changed, 56 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index ce464f3..aa7bd50 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ http: metrics: enable_jvm: true enable_process: true + entity_snapshot_interval_seconds: 15 movement: count_block_changes_only: true ``` @@ -77,7 +78,7 @@ The Gitea workflow expects these secrets/variables: ## Metrics -Some metrics depend on server implementations (TPS/MSPT, player ping, tile entities). If a method is unavailable on your server build, those metrics are omitted. +Some metrics depend on server implementations (TPS/MSPT, player ping, tile entities). If a method is unavailable on your server build, those metrics are omitted. Entity and tile counts are updated on a periodic main-thread snapshot. | Metric | Type | Labels | Description | | --- | --- | --- | --- | diff --git a/src/main/java/com/prometheus/spigot/PrometheusSpigotPlugin.java b/src/main/java/com/prometheus/spigot/PrometheusSpigotPlugin.java index 34b12e2..55b33b0 100644 --- a/src/main/java/com/prometheus/spigot/PrometheusSpigotPlugin.java +++ b/src/main/java/com/prometheus/spigot/PrometheusSpigotPlugin.java @@ -17,7 +17,9 @@ import java.net.InetSocketAddress; import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; public final class PrometheusSpigotPlugin extends JavaPlugin { private HttpServer httpServer; @@ -25,6 +27,7 @@ public final class PrometheusSpigotPlugin extends JavaPlugin { private MetricsRegistry metricsRegistry; private String bearerToken; private boolean countBlockMovementOnly; + private BukkitTask snapshotTask; @Override public void onEnable() { @@ -43,6 +46,8 @@ public final class PrometheusSpigotPlugin extends JavaPlugin { metricsRegistry = new MetricsRegistry(getServer(), enableJvm, enableProcess); + scheduleSnapshotTask(); + registerListeners(); startHttpServer(); } @@ -57,6 +62,10 @@ public final class PrometheusSpigotPlugin extends JavaPlugin { httpExecutor.shutdownNow(); httpExecutor = null; } + if (snapshotTask != null) { + snapshotTask.cancel(); + snapshotTask = null; + } } private void registerListeners() { @@ -95,4 +104,12 @@ public final class PrometheusSpigotPlugin extends JavaPlugin { httpServer.start(); getLogger().info(String.format(Locale.ROOT, "Prometheus endpoint listening on http://%s:%d%s", bindAddress, port, path)); } + + private void scheduleSnapshotTask() { + int intervalSeconds = getConfig().getInt("metrics.entity_snapshot_interval_seconds", 15); + long intervalTicks = Math.max(20L, intervalSeconds * 20L); + + metricsRegistry.refreshEntitySnapshots(); + snapshotTask = Bukkit.getScheduler().runTaskTimer(this, metricsRegistry::refreshEntitySnapshots, intervalTicks, intervalTicks); + } } diff --git a/src/main/java/com/prometheus/spigot/metrics/MetricsRegistry.java b/src/main/java/com/prometheus/spigot/metrics/MetricsRegistry.java index 666a8c8..6e78d60 100644 --- a/src/main/java/com/prometheus/spigot/metrics/MetricsRegistry.java +++ b/src/main/java/com/prometheus/spigot/metrics/MetricsRegistry.java @@ -6,6 +6,7 @@ import java.lang.management.MemoryUsage; import java.lang.management.ThreadMXBean; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -31,6 +32,8 @@ public final class MetricsRegistry { private final Method serverMsptMethod; private final Method playerPingMethod; private final Method chunkTileEntitiesMethod; + private volatile Map entityCountsSnapshot = Collections.emptyMap(); + private volatile Map tileEntityCountsSnapshot = Collections.emptyMap(); private final LongAdder chatMessages = new LongAdder(); private final LongAdder playerMovements = new LongAdder(); @@ -120,6 +123,37 @@ public final class MetricsRegistry { pluginDisables.increment(); } + public void refreshEntitySnapshots() { + Map entityCounts = new HashMap<>(); + Map tileCounts = new HashMap<>(); + + for (var world : server.getWorlds()) { + for (Entity entity : world.getEntities()) { + EntityType type = entity.getType(); + String label = normalizeLabel(type.name()); + entityCounts.merge(label, 1L, Long::sum); + } + + if (chunkTileEntitiesMethod != null) { + for (Chunk chunk : world.getLoadedChunks()) { + Object result = invoke(chunkTileEntitiesMethod, chunk); + if (!(result instanceof Object[] states)) { + continue; + } + for (Object state : states) { + if (state instanceof BlockState blockState) { + String label = normalizeLabel(blockState.getType().name()); + tileCounts.merge(label, 1L, Long::sum); + } + } + } + } + } + + entityCountsSnapshot = Collections.unmodifiableMap(entityCounts); + tileEntityCountsSnapshot = Collections.unmodifiableMap(tileCounts); + } + public String render() { StringBuilder builder = new StringBuilder(); @@ -226,37 +260,14 @@ public final class MetricsRegistry { } private void appendEntityMetrics(StringBuilder builder) { - Map entityCounts = new HashMap<>(); - for (var world : server.getWorlds()) { - for (Entity entity : world.getEntities()) { - EntityType type = entity.getType(); - String label = normalizeLabel(type.name()); - entityCounts.merge(label, 1L, Long::sum); - } - } - appendLabeledGauge(builder, "spigot_entities_total", "Entities by type", "type", entityCounts); + appendLabeledGauge(builder, "spigot_entities_total", "Entities by type", "type", entityCountsSnapshot); } private void appendTileEntityMetrics(StringBuilder builder) { if (chunkTileEntitiesMethod == null) { return; } - Map tileCounts = new HashMap<>(); - for (var world : server.getWorlds()) { - for (Chunk chunk : world.getLoadedChunks()) { - Object result = invoke(chunkTileEntitiesMethod, chunk); - if (!(result instanceof Object[] states)) { - continue; - } - for (Object state : states) { - if (state instanceof BlockState blockState) { - String label = normalizeLabel(blockState.getType().name()); - tileCounts.merge(label, 1L, Long::sum); - } - } - } - } - appendLabeledGauge(builder, "spigot_tile_entities_total", "Tile entities by type", "type", tileCounts); + appendLabeledGauge(builder, "spigot_tile_entities_total", "Tile entities by type", "type", tileEntityCountsSnapshot); } private void appendPlayerPingMetrics(StringBuilder builder) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5f5c0a5..934c072 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -6,5 +6,6 @@ http: metrics: enable_jvm: true enable_process: true + entity_snapshot_interval_seconds: 15 movement: count_block_changes_only: true