|
|
|
@@ -1,15 +1,25 @@
|
|
|
|
|
package com.prometheus.spigot.metrics;
|
|
|
|
|
|
|
|
|
|
import java.lang.management.GarbageCollectorMXBean;
|
|
|
|
|
import java.lang.management.ManagementFactory;
|
|
|
|
|
import java.lang.management.MemoryUsage;
|
|
|
|
|
import java.lang.management.ThreadMXBean;
|
|
|
|
|
import java.lang.reflect.Method;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Comparator;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Locale;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
import java.util.concurrent.LongAdder;
|
|
|
|
|
import org.bukkit.Chunk;
|
|
|
|
|
import org.bukkit.Material;
|
|
|
|
|
import org.bukkit.Server;
|
|
|
|
|
import org.bukkit.block.BlockState;
|
|
|
|
|
import org.bukkit.entity.Entity;
|
|
|
|
|
import org.bukkit.entity.EntityType;
|
|
|
|
|
import org.bukkit.entity.Player;
|
|
|
|
|
import org.bukkit.event.entity.EntityDamageEvent;
|
|
|
|
|
|
|
|
|
|
public final class MetricsRegistry {
|
|
|
|
@@ -17,17 +27,35 @@ public final class MetricsRegistry {
|
|
|
|
|
private final boolean enableJvm;
|
|
|
|
|
private final boolean enableProcess;
|
|
|
|
|
private final long startTimeMillis;
|
|
|
|
|
private final Method serverTpsMethod;
|
|
|
|
|
private final Method serverMsptMethod;
|
|
|
|
|
private final Method playerPingMethod;
|
|
|
|
|
private final Method chunkTileEntitiesMethod;
|
|
|
|
|
|
|
|
|
|
private final LongAdder chatMessages = new LongAdder();
|
|
|
|
|
private final LongAdder playerMovements = new LongAdder();
|
|
|
|
|
private final LongAdder playerJoins = new LongAdder();
|
|
|
|
|
private final LongAdder playerQuits = new LongAdder();
|
|
|
|
|
private final LongAdder chunkLoads = new LongAdder();
|
|
|
|
|
private final LongAdder chunkUnloads = new LongAdder();
|
|
|
|
|
private final LongAdder chunkGenerations = new LongAdder();
|
|
|
|
|
private final LongAdder pluginEnables = new LongAdder();
|
|
|
|
|
private final LongAdder pluginDisables = new LongAdder();
|
|
|
|
|
private final ConcurrentHashMap<String, LongAdder> blocksBrokenByMaterial = new ConcurrentHashMap<>();
|
|
|
|
|
private final ConcurrentHashMap<String, LongAdder> deathsByCause = new ConcurrentHashMap<>();
|
|
|
|
|
private final ConcurrentHashMap<String, LongAdder> loginFailuresByReason = new ConcurrentHashMap<>();
|
|
|
|
|
private final ConcurrentHashMap<String, LongAdder> commandsBlocked = new ConcurrentHashMap<>();
|
|
|
|
|
private final ConcurrentHashMap<LabelPair, LongAdder> commandsExecuted = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
public MetricsRegistry(Server server, boolean enableJvm, boolean enableProcess) {
|
|
|
|
|
this.server = server;
|
|
|
|
|
this.enableJvm = enableJvm;
|
|
|
|
|
this.enableProcess = enableProcess;
|
|
|
|
|
this.startTimeMillis = System.currentTimeMillis();
|
|
|
|
|
this.serverTpsMethod = findMethod(server.getClass(), "getTPS");
|
|
|
|
|
this.serverMsptMethod = findMethod(server.getClass(), "getAverageTickTime");
|
|
|
|
|
this.playerPingMethod = findMethod(Player.class, "getPing");
|
|
|
|
|
this.chunkTileEntitiesMethod = findMethod(Chunk.class, "getTileEntities");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementChatMessages() {
|
|
|
|
@@ -38,6 +66,18 @@ public final class MetricsRegistry {
|
|
|
|
|
playerMovements.increment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementPlayerJoins() {
|
|
|
|
|
playerJoins.increment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementPlayerQuits() {
|
|
|
|
|
playerQuits.increment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementPlayerLoginFailed(String reason) {
|
|
|
|
|
incrementLabeledCounter(loginFailuresByReason, reason);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementBlocksBroken(Material material) {
|
|
|
|
|
incrementLabeledCounter(blocksBrokenByMaterial, material.name());
|
|
|
|
|
}
|
|
|
|
@@ -50,18 +90,67 @@ public final class MetricsRegistry {
|
|
|
|
|
incrementLabeledCounter(deathsByCause, cause);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementChunkLoads() {
|
|
|
|
|
chunkLoads.increment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementChunkUnloads() {
|
|
|
|
|
chunkUnloads.increment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementChunkGenerations() {
|
|
|
|
|
chunkGenerations.increment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementCommandExecuted(String command, String source) {
|
|
|
|
|
String normalizedCommand = normalizeLabel(command);
|
|
|
|
|
String normalizedSource = normalizeLabel(source);
|
|
|
|
|
commandsExecuted.computeIfAbsent(new LabelPair(normalizedCommand, normalizedSource), key -> new LongAdder()).increment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementCommandBlocked(String command) {
|
|
|
|
|
incrementLabeledCounter(commandsBlocked, command);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementPluginEnables() {
|
|
|
|
|
pluginEnables.increment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void incrementPluginDisables() {
|
|
|
|
|
pluginDisables.increment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String render() {
|
|
|
|
|
StringBuilder builder = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
appendCounter(builder, "spigot_chat_messages_total", "Total chat messages sent", chatMessages.sum());
|
|
|
|
|
appendCounter(builder, "spigot_player_movements_total", "Total player movement events", playerMovements.sum());
|
|
|
|
|
appendCounter(builder, "spigot_players_joined_total", "Total player joins", playerJoins.sum());
|
|
|
|
|
appendCounter(builder, "spigot_players_quit_total", "Total player quits", playerQuits.sum());
|
|
|
|
|
appendCounter(builder, "spigot_chunk_loads_total", "Total chunk load events", chunkLoads.sum());
|
|
|
|
|
appendCounter(builder, "spigot_chunk_unloads_total", "Total chunk unload events", chunkUnloads.sum());
|
|
|
|
|
appendCounter(builder, "spigot_chunks_generated_total", "Total generated chunks", chunkGenerations.sum());
|
|
|
|
|
appendCounter(builder, "spigot_plugin_enable_total", "Total plugin enable events", pluginEnables.sum());
|
|
|
|
|
appendCounter(builder, "spigot_plugin_disable_total", "Total plugin disable events", pluginDisables.sum());
|
|
|
|
|
|
|
|
|
|
appendLabeledCounter(builder, "spigot_blocks_broken_total", "Blocks broken by material", "material", blocksBrokenByMaterial);
|
|
|
|
|
appendLabeledCounter(builder, "spigot_player_deaths_total", "Player deaths by cause", "cause", deathsByCause);
|
|
|
|
|
appendLabeledCounter(builder, "spigot_player_logins_failed_total", "Failed player logins by reason", "reason", loginFailuresByReason);
|
|
|
|
|
appendLabeledCounter(builder, "spigot_commands_blocked_total", "Blocked commands by name", "command", commandsBlocked);
|
|
|
|
|
appendLabeledCounter(builder, "spigot_commands_executed_total", "Commands executed by source", "command", "source", commandsExecuted);
|
|
|
|
|
|
|
|
|
|
appendGauge(builder, "spigot_players_online", "Online player count", server.getOnlinePlayers().size());
|
|
|
|
|
appendGauge(builder, "spigot_players_max", "Max player slots", server.getMaxPlayers());
|
|
|
|
|
appendGauge(builder, "spigot_worlds_loaded", "Loaded world count", server.getWorlds().size());
|
|
|
|
|
appendGauge(builder, "spigot_chunks_loaded", "Loaded chunk count", countLoadedChunks());
|
|
|
|
|
appendGauge(builder, "spigot_plugins_loaded", "Loaded plugin count", server.getPluginManager().getPlugins().length);
|
|
|
|
|
appendGauge(builder, "spigot_tasks_pending", "Pending scheduled tasks", server.getScheduler().getPendingTasks().size());
|
|
|
|
|
appendGauge(builder, "spigot_tasks_running", "Active scheduled tasks", server.getScheduler().getActiveWorkers().size());
|
|
|
|
|
|
|
|
|
|
appendEntityMetrics(builder);
|
|
|
|
|
appendTileEntityMetrics(builder);
|
|
|
|
|
appendPlayerPingMetrics(builder);
|
|
|
|
|
appendPerformanceMetrics(builder);
|
|
|
|
|
|
|
|
|
|
if (enableProcess) {
|
|
|
|
|
appendProcessMetrics(builder);
|
|
|
|
@@ -87,6 +176,147 @@ public final class MetricsRegistry {
|
|
|
|
|
|
|
|
|
|
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
|
|
|
|
|
appendGauge(builder, "jvm_threads", "Current JVM thread count", threadBean.getThreadCount());
|
|
|
|
|
appendThreadCpuMetrics(builder, threadBean);
|
|
|
|
|
appendGcMetrics(builder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendThreadCpuMetrics(StringBuilder builder, ThreadMXBean threadBean) {
|
|
|
|
|
if (!threadBean.isThreadCpuTimeSupported()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!threadBean.isThreadCpuTimeEnabled()) {
|
|
|
|
|
try {
|
|
|
|
|
threadBean.setThreadCpuTimeEnabled(true);
|
|
|
|
|
} catch (SecurityException ex) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
long totalNanos = 0L;
|
|
|
|
|
for (long threadId : threadBean.getAllThreadIds()) {
|
|
|
|
|
long time = threadBean.getThreadCpuTime(threadId);
|
|
|
|
|
if (time > 0) {
|
|
|
|
|
totalNanos += time;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
double totalSeconds = totalNanos / 1_000_000_000.0;
|
|
|
|
|
appendCounter(builder, "jvm_thread_cpu_seconds_total", "Total CPU seconds used by JVM threads", formatDouble(totalSeconds));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendGcMetrics(StringBuilder builder) {
|
|
|
|
|
Map<String, Long> gcCounts = new HashMap<>();
|
|
|
|
|
Map<String, String> gcTimes = new HashMap<>();
|
|
|
|
|
for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
|
|
|
|
|
String name = normalizeLabel(gcBean.getName());
|
|
|
|
|
long count = gcBean.getCollectionCount();
|
|
|
|
|
long timeMillis = gcBean.getCollectionTime();
|
|
|
|
|
if (count >= 0) {
|
|
|
|
|
gcCounts.put(name, count);
|
|
|
|
|
}
|
|
|
|
|
if (timeMillis >= 0) {
|
|
|
|
|
double seconds = timeMillis / 1000.0;
|
|
|
|
|
gcTimes.put(name, formatDouble(seconds));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!gcCounts.isEmpty()) {
|
|
|
|
|
appendLabeledCounter(builder, "jvm_gc_pause_seconds_count", "GC pause count", "gc", gcCounts);
|
|
|
|
|
}
|
|
|
|
|
if (!gcTimes.isEmpty()) {
|
|
|
|
|
appendLabeledCounter(builder, "jvm_gc_pause_seconds_total", "Total GC pause time", "gc", gcTimes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendEntityMetrics(StringBuilder builder) {
|
|
|
|
|
Map<String, Long> 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendTileEntityMetrics(StringBuilder builder) {
|
|
|
|
|
if (chunkTileEntitiesMethod == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Map<String, Long> 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendPlayerPingMetrics(StringBuilder builder) {
|
|
|
|
|
if (playerPingMethod == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
List<Integer> pings = new ArrayList<>();
|
|
|
|
|
for (Player player : server.getOnlinePlayers()) {
|
|
|
|
|
Integer ping = readPing(player);
|
|
|
|
|
if (ping != null) {
|
|
|
|
|
pings.add(ping);
|
|
|
|
|
appendLabeledGauge(builder, "spigot_player_ping_ms_player", "Player ping in milliseconds", "player", player.getName(), ping);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
appendPingHistogram(builder, pings);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendPingHistogram(StringBuilder builder, List<Integer> pings) {
|
|
|
|
|
double[] buckets = {25, 50, 100, 150, 200, 300, 500, 1000};
|
|
|
|
|
long[] bucketCounts = new long[buckets.length + 1];
|
|
|
|
|
long total = 0;
|
|
|
|
|
long sum = 0;
|
|
|
|
|
for (int ping : pings) {
|
|
|
|
|
total++;
|
|
|
|
|
sum += ping;
|
|
|
|
|
boolean placed = false;
|
|
|
|
|
for (int i = 0; i < buckets.length; i++) {
|
|
|
|
|
if (ping <= buckets[i]) {
|
|
|
|
|
bucketCounts[i]++;
|
|
|
|
|
placed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!placed) {
|
|
|
|
|
bucketCounts[bucketCounts.length - 1]++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
appendHelpAndType(builder, "spigot_player_ping_ms", "Player ping histogram", "histogram");
|
|
|
|
|
long cumulative = 0;
|
|
|
|
|
for (int i = 0; i < buckets.length; i++) {
|
|
|
|
|
cumulative += bucketCounts[i];
|
|
|
|
|
appendHistogramBucket(builder, "spigot_player_ping_ms_bucket", buckets[i], cumulative);
|
|
|
|
|
}
|
|
|
|
|
cumulative += bucketCounts[bucketCounts.length - 1];
|
|
|
|
|
appendHistogramBucket(builder, "spigot_player_ping_ms_bucket", Double.POSITIVE_INFINITY, cumulative);
|
|
|
|
|
builder.append("spigot_player_ping_ms_count ").append(total).append('\n');
|
|
|
|
|
builder.append("spigot_player_ping_ms_sum ").append(sum).append('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendPerformanceMetrics(StringBuilder builder) {
|
|
|
|
|
double[] tps = readTps();
|
|
|
|
|
if (tps != null && tps.length >= 3) {
|
|
|
|
|
appendLabeledGauge(builder, "spigot_tps", "Server TPS by interval", "interval", "1m", formatDouble(tps[0]));
|
|
|
|
|
appendLabeledGauge(builder, "spigot_tps", "Server TPS by interval", "interval", "5m", formatDouble(tps[1]));
|
|
|
|
|
appendLabeledGauge(builder, "spigot_tps", "Server TPS by interval", "interval", "15m", formatDouble(tps[2]));
|
|
|
|
|
}
|
|
|
|
|
Double mspt = readMspt();
|
|
|
|
|
if (mspt != null) {
|
|
|
|
|
appendGauge(builder, "spigot_mspt", "Average milliseconds per tick", formatDouble(mspt));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendMemoryMetrics(StringBuilder builder, String area, MemoryUsage usage) {
|
|
|
|
@@ -101,6 +331,11 @@ public final class MetricsRegistry {
|
|
|
|
|
builder.append(name).append(' ').append(value).append('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendCounter(StringBuilder builder, String name, String help, String value) {
|
|
|
|
|
appendHelpAndType(builder, name, help, "counter");
|
|
|
|
|
builder.append(name).append(' ').append(value).append('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendGauge(StringBuilder builder, String name, String help, long value) {
|
|
|
|
|
appendHelpAndType(builder, name, help, "gauge");
|
|
|
|
|
builder.append(name).append(' ').append(value).append('\n');
|
|
|
|
@@ -111,6 +346,18 @@ public final class MetricsRegistry {
|
|
|
|
|
builder.append(name).append(' ').append(value).append('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendLabeledGauge(StringBuilder builder, String name, String help, String label, Map<String, Long> values) {
|
|
|
|
|
appendHelpAndType(builder, name, help, "gauge");
|
|
|
|
|
values.entrySet().stream()
|
|
|
|
|
.sorted(Map.Entry.comparingByKey(Comparator.naturalOrder()))
|
|
|
|
|
.forEach(entry -> builder.append(name)
|
|
|
|
|
.append('{').append(label).append("=\"")
|
|
|
|
|
.append(escapeLabelValue(entry.getKey()))
|
|
|
|
|
.append("\"} ")
|
|
|
|
|
.append(entry.getValue())
|
|
|
|
|
.append('\n'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendLabeledCounter(StringBuilder builder, String name, String help, String label, Map<String, LongAdder> values) {
|
|
|
|
|
appendHelpAndType(builder, name, help, "counter");
|
|
|
|
|
values.entrySet().stream()
|
|
|
|
@@ -123,6 +370,48 @@ public final class MetricsRegistry {
|
|
|
|
|
.append('\n'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendLabeledCounter(StringBuilder builder, String name, String help, String label, Map<String, Long> values) {
|
|
|
|
|
appendHelpAndType(builder, name, help, "counter");
|
|
|
|
|
values.entrySet().stream()
|
|
|
|
|
.sorted(Map.Entry.comparingByKey(Comparator.naturalOrder()))
|
|
|
|
|
.forEach(entry -> builder.append(name)
|
|
|
|
|
.append('{').append(label).append("=\"")
|
|
|
|
|
.append(escapeLabelValue(entry.getKey()))
|
|
|
|
|
.append("\"} ")
|
|
|
|
|
.append(entry.getValue())
|
|
|
|
|
.append('\n'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendLabeledCounter(StringBuilder builder, String name, String help, String label, Map<String, String> values) {
|
|
|
|
|
appendHelpAndType(builder, name, help, "counter");
|
|
|
|
|
values.entrySet().stream()
|
|
|
|
|
.sorted(Map.Entry.comparingByKey(Comparator.naturalOrder()))
|
|
|
|
|
.forEach(entry -> builder.append(name)
|
|
|
|
|
.append('{').append(label).append("=\"")
|
|
|
|
|
.append(escapeLabelValue(entry.getKey()))
|
|
|
|
|
.append("\"} ")
|
|
|
|
|
.append(entry.getValue())
|
|
|
|
|
.append('\n'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendLabeledCounter(StringBuilder builder, String name, String help, String labelOne, String labelTwo,
|
|
|
|
|
Map<LabelPair, LongAdder> values) {
|
|
|
|
|
appendHelpAndType(builder, name, help, "counter");
|
|
|
|
|
values.entrySet().stream()
|
|
|
|
|
.sorted(Map.Entry.comparingByKey(Comparator.comparing(LabelPair::first).thenComparing(LabelPair::second)))
|
|
|
|
|
.forEach(entry -> builder.append(name)
|
|
|
|
|
.append('{')
|
|
|
|
|
.append(labelOne).append("=\"")
|
|
|
|
|
.append(escapeLabelValue(entry.getKey().first()))
|
|
|
|
|
.append("\",")
|
|
|
|
|
.append(labelTwo).append("=\"")
|
|
|
|
|
.append(escapeLabelValue(entry.getKey().second()))
|
|
|
|
|
.append("\"} ")
|
|
|
|
|
.append(entry.getValue().sum())
|
|
|
|
|
.append('\n'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void appendLabeledGauge(StringBuilder builder, String name, String help, String label, String labelValue, long value) {
|
|
|
|
|
appendHelpAndType(builder, name, help, "gauge");
|
|
|
|
|
builder.append(name)
|
|
|
|
@@ -133,16 +422,97 @@ public final class MetricsRegistry {
|
|
|
|
|
.append('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendLabeledGauge(StringBuilder builder, String name, String help, String label, String labelValue, String value) {
|
|
|
|
|
appendHelpAndType(builder, name, help, "gauge");
|
|
|
|
|
builder.append(name)
|
|
|
|
|
.append('{').append(label).append("=\"")
|
|
|
|
|
.append(escapeLabelValue(labelValue))
|
|
|
|
|
.append("\"} ")
|
|
|
|
|
.append(value)
|
|
|
|
|
.append('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendHelpAndType(StringBuilder builder, String name, String help, String type) {
|
|
|
|
|
builder.append("# HELP ").append(name).append(' ').append(help).append('\n');
|
|
|
|
|
builder.append("# TYPE ").append(name).append(' ').append(type).append('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void incrementLabeledCounter(ConcurrentHashMap<String, LongAdder> map, String labelValue) {
|
|
|
|
|
String normalized = labelValue.toLowerCase(Locale.ROOT);
|
|
|
|
|
String normalized = normalizeLabel(labelValue);
|
|
|
|
|
map.computeIfAbsent(normalized, key -> new LongAdder()).increment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private long countLoadedChunks() {
|
|
|
|
|
long total = 0;
|
|
|
|
|
for (var world : server.getWorlds()) {
|
|
|
|
|
total += world.getLoadedChunks().length;
|
|
|
|
|
}
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String normalizeLabel(String labelValue) {
|
|
|
|
|
String trimmed = labelValue.trim();
|
|
|
|
|
return trimmed.isEmpty() ? "unknown" : trimmed.toLowerCase(Locale.ROOT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Integer readPing(Player player) {
|
|
|
|
|
if (playerPingMethod == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
Object result = invoke(playerPingMethod, player);
|
|
|
|
|
if (result instanceof Integer ping) {
|
|
|
|
|
return ping;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private double[] readTps() {
|
|
|
|
|
if (serverTpsMethod == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
Object result = invoke(serverTpsMethod, server);
|
|
|
|
|
if (result instanceof double[] tps) {
|
|
|
|
|
return tps;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Double readMspt() {
|
|
|
|
|
if (serverMsptMethod == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
Object result = invoke(serverMsptMethod, server);
|
|
|
|
|
if (result instanceof Double value) {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Object invoke(Method method, Object target) {
|
|
|
|
|
try {
|
|
|
|
|
return method.invoke(target);
|
|
|
|
|
} catch (ReflectiveOperationException ex) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Method findMethod(Class<?> type, String name) {
|
|
|
|
|
try {
|
|
|
|
|
return type.getMethod(name);
|
|
|
|
|
} catch (NoSuchMethodException ex) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendHistogramBucket(StringBuilder builder, String name, double upperBound, long value) {
|
|
|
|
|
builder.append(name)
|
|
|
|
|
.append("{le=\"")
|
|
|
|
|
.append(Double.isInfinite(upperBound) ? "+Inf" : formatDouble(upperBound))
|
|
|
|
|
.append("\"} ")
|
|
|
|
|
.append(value)
|
|
|
|
|
.append('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String escapeLabelValue(String value) {
|
|
|
|
|
return value.replace("\\", "\\\\")
|
|
|
|
|
.replace("\n", "\\n")
|
|
|
|
@@ -152,4 +522,7 @@ public final class MetricsRegistry {
|
|
|
|
|
private String formatDouble(double value) {
|
|
|
|
|
return String.format(Locale.ROOT, "%.3f", value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private record LabelPair(String first, String second) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|