From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Bacteriawa Date: Tue, 14 Oct 2025 00:28:47 +0800 Subject: [PATCH] Improved Server Health Report diff --git a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java index 5e6b490ee58a90fd7c02fa09093830c0d9c67f6b..2de0f538a5253a10e353e639e739361030b6ba02 100644 --- a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java +++ b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java @@ -1,7 +1,6 @@ package io.papermc.paper.threadedregions.commands; import io.papermc.paper.threadedregions.RegionizedServer; -import io.papermc.paper.threadedregions.RegionizedWorldData; import io.papermc.paper.threadedregions.ThreadedRegionizer; import io.papermc.paper.threadedregions.TickData; import io.papermc.paper.threadedregions.TickRegionScheduler; @@ -24,11 +23,17 @@ import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.RuntimeMXBean; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.concurrent.TimeUnit; public final class CommandServerHealth extends Command { @@ -45,7 +50,7 @@ public final class CommandServerHealth extends Command { private static final TextColor HEADER = TextColor.color(79, 164, 240); private static final TextColor PRIMARY = TextColor.color(48, 145, 237); private static final TextColor SECONDARY = TextColor.color(104, 177, 240); - private static final TextColor INFORMATION = TextColor.color(145, 198, 243); + private static final TextColor INFORMATION = TextColor.color(180, 220, 255); private static final TextColor LIST = TextColor.color(33, 97, 188); public CommandServerHealth() { @@ -55,33 +60,50 @@ public final class CommandServerHealth extends Command { this.setPermission("bukkit.command.tps"); } + private static String formatUptime(long uptimeMillis) { + long days = TimeUnit.MILLISECONDS.toDays(uptimeMillis); + long hours = TimeUnit.MILLISECONDS.toHours(uptimeMillis) % 24; + long minutes = TimeUnit.MILLISECONDS.toMinutes(uptimeMillis) % 60; + long seconds = TimeUnit.MILLISECONDS.toSeconds(uptimeMillis) % 60; + + StringBuilder sb = new StringBuilder(); + if (days > 0) sb.append(days).append("d "); + if (hours > 0) sb.append(hours).append("h "); + if (minutes > 0) sb.append(minutes).append("m "); + sb.append(seconds).append("s"); + + return sb.toString(); + } + private static Component formatRegionInfo(final String prefix, final double util, final double mspt, final double tps, final boolean newline) { return Component.text() - .append(Component.text(prefix, PRIMARY, TextDecoration.BOLD)) - .append(Component.text(ONE_DECIMAL_PLACES.get().format(util * 100.0), CommandUtil.getUtilisationColourRegion(util))) - .append(Component.text("% util at ", PRIMARY)) - .append(Component.text(TWO_DECIMAL_PLACES.get().format(mspt), CommandUtil.getColourForMSPT(mspt))) - .append(Component.text(" MSPT at ", PRIMARY)) - .append(Component.text(TWO_DECIMAL_PLACES.get().format(tps), CommandUtil.getColourForTPS(tps))) - .append(Component.text(" TPS" + (newline ? "\n" : ""), PRIMARY)) - .build(); + .append(Component.text(prefix, PRIMARY, TextDecoration.BOLD)) + .append(Component.text(ONE_DECIMAL_PLACES.get().format(util * 100.0), CommandUtil.getUtilisationColourRegion(util))) + .append(Component.text("% util", PRIMARY)) + .append(Component.text(" | ", SECONDARY)) + .append(Component.text(TWO_DECIMAL_PLACES.get().format(mspt), CommandUtil.getColourForMSPT(mspt))) + .append(Component.text(" mspt", PRIMARY)) + .append(Component.text(" | ", SECONDARY)) + .append(Component.text(TWO_DECIMAL_PLACES.get().format(tps), CommandUtil.getColourForTPS(tps))) + .append(Component.text(" TPS" + (newline ? "\n" : ""), PRIMARY)) + .build(); } private static Component formatRegionStats(final TickRegions.RegionStats stats, final boolean newline) { return Component.text() - .append(Component.text("Chunks: ", PRIMARY)) - .append(Component.text(NO_DECIMAL_PLACES.get().format((long)stats.getChunkCount()), INFORMATION)) - .append(Component.text(" Players: ", PRIMARY)) - .append(Component.text(NO_DECIMAL_PLACES.get().format((long)stats.getPlayerCount()), INFORMATION)) - .append(Component.text(" Entities: ", PRIMARY)) - .append(Component.text(NO_DECIMAL_PLACES.get().format((long)stats.getEntityCount()) + (newline ? "\n" : ""), INFORMATION)) - .build(); + .append(Component.text("Chunks: ", PRIMARY)) + .append(Component.text(NO_DECIMAL_PLACES.get().format((long)stats.getChunkCount()), INFORMATION)) + .append(Component.text(" Players: ", PRIMARY)) + .append(Component.text(NO_DECIMAL_PLACES.get().format((long)stats.getPlayerCount()), INFORMATION)) + .append(Component.text(" Entities: ", PRIMARY)) + .append(Component.text(NO_DECIMAL_PLACES.get().format((long)stats.getEntityCount()) + (newline ? "\n" : ""), INFORMATION)) + .build(); } private static boolean executeRegion(final CommandSender sender, final String commandLabel, final String[] args) { final ThreadedRegionizer.ThreadedRegion region = - TickRegionScheduler.getCurrentRegion(); + TickRegionScheduler.getCurrentRegion(); if (region == null) { sender.sendMessage(Component.text("You are not in a region currently", NamedTextColor.RED)); return true; @@ -105,25 +127,17 @@ public final class CommandServerHealth extends Command { final double tps1m = report1m.tpsData().segmentAll().average(); final double mspt1m = report1m.timePerTickData().segmentAll().average() / 1.0E6; - final int yLoc = 80; - final String location = "[w:'" + world.getWorld().getName() + "'," + centerBlockX + "," + yLoc + "," + centerBlockZ + "]"; + final String location = world.getWorld().getName() + " (" + centerBlockX + ", " + centerBlockZ + ")"; final Component line = Component.text() - .append(Component.text("Region around block ", PRIMARY)) - .append(Component.text(location, INFORMATION)) - .append(Component.text(":\n", PRIMARY)) - - .append( - formatRegionInfo("15s: ", util15s, mspt15s, tps15s, true) - ) - .append( - formatRegionInfo("1m: ", util1m, mspt1m, tps1m, true) - ) - .append( - formatRegionStats(region.getData().getRegionStats(), false) - ) - - .build(); + .append(Component.text("Region around block ", PRIMARY)) + .append(Component.text(location, INFORMATION)) + .append(Component.text(":\n", PRIMARY)) + + .append(formatRegionInfo("15s: ", util15s, mspt15s, tps15s, true)) + .append(formatRegionInfo("1m: ", util1m, mspt1m, tps1m, true)) + .append(formatRegionStats(region.getData().getRegionStats(), false)) + .build(); sender.sendMessage(line); @@ -144,7 +158,7 @@ public final class CommandServerHealth extends Command { } final List> regions = - new ArrayList<>(); + new ArrayList<>(); for (final World bukkitWorld : Bukkit.getWorlds()) { final ServerLevel world = ((CraftWorld)bukkitWorld).getHandle(); @@ -163,6 +177,18 @@ public final class CommandServerHealth extends Command { final long currTime = System.nanoTime(); final TickData.TickReportData globalTickReport = RegionizedServer.getGlobalTickData().getTickReport15s(currTime); + final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); + final MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage(); + final long usedMemory = heapUsage.getUsed() / (1024 * 1024); + final long maxMemory = heapUsage.getMax() / (1024 * 1024); + + final double memPercent = (double) usedMemory / maxMemory * 100.0; + final TextColor memColor = memPercent < 60 ? CommandUtil.getUtilisationColourRegion(0.0) : (memPercent < 85 ? NamedTextColor.YELLOW : NamedTextColor.RED); + + final RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); + final long uptime = runtimeBean.getUptime(); + final String uptimeStr = formatUptime(uptime); + for (final ThreadedRegionizer.ThreadedRegion region : regions) { final TickData.TickReportData report = region.getData().getRegionSchedulingHandle().getTickReport15s(currTime); tpsByRegion.add(report == null ? 20.0 : report.tpsData().segmentAll().average()); @@ -174,31 +200,27 @@ public final class CommandServerHealth extends Command { final double loadRate = ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkFullTask.loadRate(currTime); totalUtil += globalTickReport.utilisation(); + final TextColor utilisationColor = CommandUtil.getUtilisationColourRegion(totalUtil / (double)maxThreadCount); tpsByRegion.sort(null); if (!tpsByRegion.isEmpty()) { minTps = tpsByRegion.getDouble(0); maxTps = tpsByRegion.getDouble(tpsByRegion.size() - 1); - final int middle = tpsByRegion.size() >> 1; if ((tpsByRegion.size() & 1) == 0) { - // even, average the two middle points medianTps = (tpsByRegion.getDouble(middle - 1) + tpsByRegion.getDouble(middle)) / 2.0; } else { - // odd, can just grab middle medianTps = tpsByRegion.getDouble(middle); } } else { - // no regions = green minTps = medianTps = maxTps = 20.0; } final List, TickData.TickReportData>> - regionsBelowThreshold = new ArrayList<>(); + regionsBelowThreshold = new ArrayList<>(); for (int i = 0, len = regions.size(); i < len; ++i) { final TickData.TickReportData report = reportsByRegion.get(i); - regionsBelowThreshold.add(new ObjectObjectImmutablePair<>(regions.get(i), report)); } @@ -207,11 +229,18 @@ public final class CommandServerHealth extends Command { final TickData.TickReportData report2 = p2.right(); final double util1 = report1 == null ? 0.0 : report1.utilisation(); final double util2 = report2 == null ? 0.0 : report2.utilisation(); - - // we want the largest first return Double.compare(util2, util1); }); + long totalChunks = 0; + long totalEntities = 0; + + for (final ThreadedRegionizer.ThreadedRegion region : regions) { + final TickRegions.RegionStats stats = region.getData().getRegionStats(); + totalChunks += stats.getChunkCount(); + totalEntities += stats.getEntityCount(); + } + final TextComponent.Builder lowestRegionsBuilder = Component.text(); if (sender instanceof Player) { @@ -219,23 +248,18 @@ public final class CommandServerHealth extends Command { } for (int i = 0, len = Math.min(lowestRegionsCount, regionsBelowThreshold.size()); i < len; ++i) { final ObjectObjectImmutablePair, TickData.TickReportData> - pair = regionsBelowThreshold.get(i); + pair = regionsBelowThreshold.get(i); final TickData.TickReportData report = pair.right(); final ThreadedRegionizer.ThreadedRegion region = - pair.left(); + pair.left(); - if (report == null) { - // skip regions with no data - continue; - } + if (report == null) continue; final ServerLevel world = region.regioniser.world; final ChunkPos chunkCenter = region.getCenterChunk(); - if (chunkCenter == null) { - // region does not exist anymore - continue; - } + if (chunkCenter == null) continue; + final int centerBlockX = ((chunkCenter.x << 4) | 7); final int centerBlockZ = ((chunkCenter.z << 4) | 7); final double util = report.utilisation(); @@ -243,75 +267,102 @@ public final class CommandServerHealth extends Command { final double mspt = report.timePerTickData().segmentAll().average() / 1.0E6; final int yLoc = 80; - final String location = "[w:'" + world.getWorld().getName() + "'," + centerBlockX + "," + yLoc + "," + centerBlockZ + "]"; - final Component line = Component.text() - .append(Component.text(" - ", LIST, TextDecoration.BOLD)) - .append(Component.text("Region around block ", PRIMARY)) - .append(Component.text(location, INFORMATION)) - .append(Component.text(":\n", PRIMARY)) - - .append(Component.text(" ", PRIMARY)) - .append(Component.text(ONE_DECIMAL_PLACES.get().format(util * 100.0), CommandUtil.getUtilisationColourRegion(util))) - .append(Component.text("% util at ", PRIMARY)) - .append(Component.text(TWO_DECIMAL_PLACES.get().format(mspt), CommandUtil.getColourForMSPT(mspt))) - .append(Component.text(" MSPT at ", PRIMARY)) - .append(Component.text(TWO_DECIMAL_PLACES.get().format(tps), CommandUtil.getColourForTPS(tps))) - .append(Component.text(" TPS\n", PRIMARY)) - - .append(Component.text(" ", PRIMARY)) - .append(formatRegionStats(region.getData().getRegionStats(), (i + 1) != len)) - .build() + final String location = world.getWorld().getName() + " (" + centerBlockX + ", " + centerBlockZ + ")"; - .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey().toString() + " run tp " + centerBlockX + ".5 " + yLoc + " " + centerBlockZ + ".5")) - .hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("Click to teleport to " + location, SECONDARY))); + final Component line = Component.text() + .append(Component.text(" - ", LIST, TextDecoration.BOLD)) + .append(Component.text("Region at ", PRIMARY)) + .append(Component.text(location, INFORMATION)) + .append(Component.text(":\n", PRIMARY)) + + .append(Component.text(" ", PRIMARY)) + .append(Component.text(ONE_DECIMAL_PLACES.get().format(util * 100.0), CommandUtil.getUtilisationColourRegion(util))) + .append(Component.text("% util", PRIMARY)) + .append(Component.text(" | ", SECONDARY)) + .append(Component.text(TWO_DECIMAL_PLACES.get().format(mspt), CommandUtil.getColourForMSPT(mspt))) + .append(Component.text(" mspt", PRIMARY)) + .append(Component.text(" | ", SECONDARY)) + .append(Component.text(TWO_DECIMAL_PLACES.get().format(tps), CommandUtil.getColourForTPS(tps))) + .append(Component.text(" TPS\n", PRIMARY)) + + .append(Component.text(" ", PRIMARY)) + .append(formatRegionStats(region.getData().getRegionStats(), (i + 1) != len)) + .build() + + .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey().toString() + " run tp " + centerBlockX + ".5 " + yLoc + " " + centerBlockZ + ".5")) + .hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("Click to teleport to " + location, SECONDARY))); lowestRegionsBuilder.append(line); } sender.sendMessage( - Component.text() - .append(Component.text("Server Health Report\n", HEADER, TextDecoration.BOLD)) - - .append(Component.text(" - ", LIST, TextDecoration.BOLD)) - .append(Component.text("Online Players: ", PRIMARY)) - .append(Component.text(Bukkit.getOnlinePlayers().size() + "\n", INFORMATION)) - - .append(Component.text(" - ", LIST, TextDecoration.BOLD)) - .append(Component.text("Total regions: ", PRIMARY)) - .append(Component.text(regions.size() + "\n", INFORMATION)) - - .append(Component.text(" - ", LIST, TextDecoration.BOLD)) - .append(Component.text("Utilisation: ", PRIMARY)) - .append(Component.text(ONE_DECIMAL_PLACES.get().format(totalUtil * 100.0), CommandUtil.getUtilisationColourRegion(totalUtil / (double)maxThreadCount))) - .append(Component.text("% / ", PRIMARY)) - .append(Component.text(ONE_DECIMAL_PLACES.get().format(maxThreadCount * 100.0), INFORMATION)) - .append(Component.text("%\n", PRIMARY)) - - .append(Component.text(" - ", LIST, TextDecoration.BOLD)) - .append(Component.text("Load rate: ", PRIMARY)) - .append(Component.text(TWO_DECIMAL_PLACES.get().format(loadRate) + ", ", INFORMATION)) - .append(Component.text("Gen rate: ", PRIMARY)) - .append(Component.text(TWO_DECIMAL_PLACES.get().format(genRate) + "\n", INFORMATION)) - - .append(Component.text(" - ", LIST, TextDecoration.BOLD)) - .append(Component.text("Lowest Region TPS: ", PRIMARY)) - .append(Component.text(TWO_DECIMAL_PLACES.get().format(minTps) + "\n", CommandUtil.getColourForTPS(minTps))) - - - .append(Component.text(" - ", LIST, TextDecoration.BOLD)) - .append(Component.text("Median Region TPS: ", PRIMARY)) - .append(Component.text(TWO_DECIMAL_PLACES.get().format(medianTps) + "\n", CommandUtil.getColourForTPS(medianTps))) - - .append(Component.text(" - ", LIST, TextDecoration.BOLD)) - .append(Component.text("Highest Region TPS: ", PRIMARY)) - .append(Component.text(TWO_DECIMAL_PLACES.get().format(maxTps) + "\n", CommandUtil.getColourForTPS(maxTps))) - - .append(Component.text("Highest ", HEADER, TextDecoration.BOLD)) - .append(Component.text(Integer.toString(lowestRegionsCount), INFORMATION, TextDecoration.BOLD)) - .append(Component.text(" utilisation regions\n", HEADER, TextDecoration.BOLD)) - - .append(lowestRegionsBuilder.build()) - .build() + Component.text() + .append(Component.text("Server Health Report", HEADER, TextDecoration.BOLD)) + .append(Component.text(" (Uptime: ", SECONDARY)) + .append(Component.text(uptimeStr, utilisationColor)) + .append(Component.text(")\n", SECONDARY)) + + .append(Component.text(" - ", LIST, TextDecoration.BOLD)) + .append(Component.text("Online Players: ", PRIMARY)) + .append(Component.text(Bukkit.getOnlinePlayers().size(), INFORMATION)) + .append(Component.newline()) + + + .append(Component.text(" - ", LIST, TextDecoration.BOLD)) + .append(Component.text("Total regions: ", PRIMARY)) + .append(Component.text(regions.size(), INFORMATION)) + .append(Component.text(", ", PRIMARY)) + .append(Component.text("Total Chunks: ", PRIMARY)) + .append(Component.text(NO_DECIMAL_PLACES.get().format(totalChunks), INFORMATION)) + .append(Component.text(", ", PRIMARY)) + .append(Component.text("Total Entities: ", PRIMARY)) + .append(Component.text(NO_DECIMAL_PLACES.get().format(totalEntities) + "\n", INFORMATION)) + + .append(Component.text(" - ", LIST, TextDecoration.BOLD)) + .append(Component.text("Utilisation: ", PRIMARY)) + .append(Component.text(ONE_DECIMAL_PLACES.get().format(totalUtil * 100.0), utilisationColor)) + .append(Component.text("%", PRIMARY)) + .append(Component.text(" / ", SECONDARY)) + .append(Component.text(ONE_DECIMAL_PLACES.get().format(maxThreadCount * 100.0), INFORMATION)) + .append(Component.text("%\n", PRIMARY)) + + .append(Component.text(" - ", LIST, TextDecoration.BOLD)) + .append(Component.text("Load rate: ", PRIMARY)) + .append(Component.text(TWO_DECIMAL_PLACES.get().format(loadRate), INFORMATION)) + .append(Component.text(", ", PRIMARY)) + .append(Component.text("Gen rate: ", PRIMARY)) + .append(Component.text(TWO_DECIMAL_PLACES.get().format(genRate) + "\n", INFORMATION)) + + .append(Component.text(" - ", LIST, TextDecoration.BOLD)) + .append(Component.text("Memory: ", PRIMARY)) + .append(Component.text(NO_DECIMAL_PLACES.get().format(usedMemory), memColor)) + .append(Component.text(" MB", memColor)) + .append(Component.text(" / ", SECONDARY)) + .append(Component.text(NO_DECIMAL_PLACES.get().format(maxMemory), INFORMATION)) + .append(Component.text(" MB", INFORMATION)) + .append(Component.text(" (", SECONDARY)) + .append(Component.text(ONE_DECIMAL_PLACES.get().format(memPercent) + "%", memColor)) + .append(Component.text(")", SECONDARY)) + .append(Component.newline()) + + .append(Component.text(" - ", LIST, TextDecoration.BOLD)) + .append(Component.text("Lowest Region TPS: ", PRIMARY)) + .append(Component.text(TWO_DECIMAL_PLACES.get().format(minTps) + "\n", CommandUtil.getColourForTPS(minTps))) + + .append(Component.text(" - ", LIST, TextDecoration.BOLD)) + .append(Component.text("Median Region TPS: ", PRIMARY)) + .append(Component.text(TWO_DECIMAL_PLACES.get().format(medianTps) + "\n", CommandUtil.getColourForTPS(medianTps))) + + .append(Component.text(" - ", LIST, TextDecoration.BOLD)) + .append(Component.text("Highest Region TPS: ", PRIMARY)) + .append(Component.text(TWO_DECIMAL_PLACES.get().format(maxTps) + "\n", CommandUtil.getColourForTPS(maxTps))) + + .append(Component.text("Highest ", HEADER, TextDecoration.BOLD)) + .append(Component.text(Integer.toString(lowestRegionsCount), INFORMATION, TextDecoration.BOLD)) + .append(Component.text(" utilisation regions\n", HEADER, TextDecoration.BOLD)) + + .append(lowestRegionsBuilder.build()) + .build() ); return true; @@ -361,4 +412,4 @@ public final class CommandServerHealth extends Command { } return new ArrayList<>(); } -} \ No newline at end of file +}