From c9ded2de1c0b52f0fb5cf417d2ebca5f3c3a1b30 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Sat, 8 Apr 2023 18:44:37 -0700 Subject: [PATCH] Add entity/player/chunk counters to /tps Required to add some basic hacks to expose a regionstats object in the TickRegions data class, but this ensures that the retrieval is thread-safe and could possibly support other data exposure for async reads. Additionally, since DecimalFormat is not thread-safe we need to use ThreadLocals to instantiate them. Change the format as well to use commas to separate groups of digits when formatting large numbers so they are easier to read. For example, 1000 becomes 1,000. --- patches/server/0005-Threaded-Regions.patch | 167 +++++++++++++++--- ...nk-system-throughput-counters-to-tps.patch | 14 +- 2 files changed, 146 insertions(+), 35 deletions(-) diff --git a/patches/server/0005-Threaded-Regions.patch b/patches/server/0005-Threaded-Regions.patch index a9bb3db..b3fedcd 100644 --- a/patches/server/0005-Threaded-Regions.patch +++ b/patches/server/0005-Threaded-Regions.patch @@ -1850,7 +1850,7 @@ index 3c17001bcd3862a76a22df488bff80a0ff4d1b83..b2fffaa862df045bacb346f3cbe7eb96 } } diff --git a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -index 61d03808c8d1ab822d9b2f31fab0de14089a3b15..9051a556fea7ee35014db7bdd75b5476f672f5a9 100644 +index 61d03808c8d1ab822d9b2f31fab0de14089a3b15..370e649a255a456d7f901b22e26241e135009af7 100644 --- a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java +++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java @@ -91,6 +91,9 @@ public final class ChunkSystem { @@ -1863,7 +1863,7 @@ index 61d03808c8d1ab822d9b2f31fab0de14089a3b15..9051a556fea7ee35014db7bdd75b5476 } public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) { -@@ -98,6 +101,9 @@ public final class ChunkSystem { +@@ -98,30 +101,34 @@ public final class ChunkSystem { for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) { chunkMap.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z); } @@ -1873,7 +1873,13 @@ index 61d03808c8d1ab822d9b2f31fab0de14089a3b15..9051a556fea7ee35014db7bdd75b5476 } public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { -@@ -109,19 +115,19 @@ public final class ChunkSystem { + chunk.playerChunk = holder; ++ chunk.level.getCurrentWorldData().addChunk(chunk); // Folia - region threading + } + + public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) { +- ++ chunk.level.getCurrentWorldData().removeChunk(chunk); // Folia - region threading } public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) { @@ -1888,7 +1894,7 @@ index 61d03808c8d1ab822d9b2f31fab0de14089a3b15..9051a556fea7ee35014db7bdd75b5476 public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { - chunk.level.getChunkSource().entityTickingChunks.add(chunk); -+ chunk.level.getCurrentWorldData().addEntityTickingChunks(chunk); // Folia - region threading ++ chunk.level.getCurrentWorldData().addEntityTickingChunk(chunk); // Folia - region threading } public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { @@ -5171,10 +5177,10 @@ index 0000000000000000000000000000000000000000..ac043fbc74874c205b821c3d2d011b92 +} diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java b/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java new file mode 100644 -index 0000000000000000000000000000000000000000..f8eae93448d1d1c70bff68ff106139b61f0171c4 +index 0000000000000000000000000000000000000000..89e6dc92bfbb28d20f252eca5257db1d3d042327 --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java -@@ -0,0 +1,649 @@ +@@ -0,0 +1,685 @@ +package io.papermc.paper.threadedregions; + +import com.destroystokyo.paper.util.maplist.ReferenceList; @@ -5198,7 +5204,6 @@ index 0000000000000000000000000000000000000000..f8eae93448d1d1c70bff68ff106139b6 +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.protocol.game.ClientboundDisconnectPacket; -+import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; @@ -5498,6 +5503,7 @@ index 0000000000000000000000000000000000000000..f8eae93448d1d1c70bff68ff106139b6 + + // ticking chunks + private final IteratorSafeOrderedReferenceSet entityTickingChunks = new IteratorSafeOrderedReferenceSet<>(); ++ private final IteratorSafeOrderedReferenceSet chunks = new IteratorSafeOrderedReferenceSet<>(); + + // Paper/CB api hook misc + // don't bother to merge/split these, no point @@ -5659,6 +5665,14 @@ index 0000000000000000000000000000000000000000..f8eae93448d1d1c70bff68ff106139b6 + } + + // entities hooks ++ public int getEntityCount() { ++ return this.allEntities.size(); ++ } ++ ++ public int getPlayerCount() { ++ return this.localPlayers.size(); ++ } ++ + public Iterable getLocalEntities() { + return this.allEntities; + } @@ -5676,6 +5690,7 @@ index 0000000000000000000000000000000000000000..f8eae93448d1d1c70bff68ff106139b6 + throw new IllegalArgumentException("Entity " + entity + " is not under this region's control"); + } + this.entityTickList.add(entity); ++ TickRegions.RegionStats.updateCurrentRegion(); + } + + public boolean hasEntityTickingEntity(final Entity entity) { @@ -5687,6 +5702,7 @@ index 0000000000000000000000000000000000000000..f8eae93448d1d1c70bff68ff106139b6 + throw new IllegalArgumentException("Entity " + entity + " is not under this region's control"); + } + this.entityTickList.remove(entity); ++ TickRegions.RegionStats.updateCurrentRegion(); + } + + public void forEachTickingEntity(final Consumer action) { @@ -5708,6 +5724,7 @@ index 0000000000000000000000000000000000000000..f8eae93448d1d1c70bff68ff106139b6 + if (entity instanceof ServerPlayer player) { + this.localPlayers.add(player); + } ++ TickRegions.RegionStats.updateCurrentRegion(); + } + } + @@ -5723,6 +5740,7 @@ index 0000000000000000000000000000000000000000..f8eae93448d1d1c70bff68ff106139b6 + if (entity instanceof ServerPlayer player) { + this.localPlayers.remove(player); + } ++ TickRegions.RegionStats.updateCurrentRegion(); + } + } + @@ -5812,17 +5830,41 @@ index 0000000000000000000000000000000000000000..f8eae93448d1d1c70bff68ff106139b6 + } + + // ticking chunks -+ public void addEntityTickingChunks(final LevelChunk levelChunk) { ++ public void addEntityTickingChunk(final LevelChunk levelChunk) { + this.entityTickingChunks.add(levelChunk); ++ TickRegions.RegionStats.updateCurrentRegion(); + } + + public void removeEntityTickingChunk(final LevelChunk levelChunk) { + this.entityTickingChunks.remove(levelChunk); ++ TickRegions.RegionStats.updateCurrentRegion(); + } + + public IteratorSafeOrderedReferenceSet getEntityTickingChunks() { + return this.entityTickingChunks; + } ++ ++ public void addChunk(final LevelChunk levelChunk) { ++ this.chunks.add(levelChunk); ++ TickRegions.RegionStats.updateCurrentRegion(); ++ } ++ ++ public void removeChunk(final LevelChunk levelChunk) { ++ this.chunks.remove(levelChunk); ++ TickRegions.RegionStats.updateCurrentRegion(); ++ } ++ ++ public IteratorSafeOrderedReferenceSet getChunks() { ++ return this.chunks; ++ } ++ ++ public int getEntityTickingChunkCount() { ++ return this.entityTickingChunks.size(); ++ } ++ ++ public int getChunkCount() { ++ return this.chunks.size(); ++ } +} diff --git a/src/main/java/io/papermc/paper/threadedregions/Schedule.java b/src/main/java/io/papermc/paper/threadedregions/Schedule.java new file mode 100644 @@ -8214,10 +8256,10 @@ index 0000000000000000000000000000000000000000..ee9f5e1f3387998cddbeb1dc6dc6e2b1 +} diff --git a/src/main/java/io/papermc/paper/threadedregions/TickRegions.java b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java new file mode 100644 -index 0000000000000000000000000000000000000000..1cc7c32690ba7f7d7cdcbe239314f30f49ecb7bc +index 0000000000000000000000000000000000000000..c6bc546b67c701f932e69630db1a5f83efc255fa --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java -@@ -0,0 +1,355 @@ +@@ -0,0 +1,399 @@ +package io.papermc.paper.threadedregions; + +import ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool; @@ -8235,6 +8277,7 @@ index 0000000000000000000000000000000000000000..1cc7c32690ba7f7d7cdcbe239314f30f +import org.slf4j.Logger; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BooleanSupplier; + @@ -8287,7 +8330,9 @@ index 0000000000000000000000000000000000000000..1cc7c32690ba7f7d7cdcbe239314f30f + + @Override + public void onRegionCreate(final ThreadedRegionizer.ThreadedRegion region) { -+ // nothing for now ++ final TickRegionData data = region.getData(); ++ // post-region merge/split regioninfo update ++ data.getRegionStats().updateFrom(data.getOrCreateRegionizedData(data.world.worldRegionData)); + } + + @Override @@ -8314,6 +8359,35 @@ index 0000000000000000000000000000000000000000..1cc7c32690ba7f7d7cdcbe239314f30f + + public static final class TickRegionSectionData implements ThreadedRegionizer.ThreadedRegionSectionData {} + ++ public static final class RegionStats { ++ ++ private final AtomicInteger entityCount = new AtomicInteger(); ++ private final AtomicInteger playerCount = new AtomicInteger(); ++ private final AtomicInteger chunkCount = new AtomicInteger(); ++ ++ public int getEntityCount() { ++ return this.entityCount.get(); ++ } ++ ++ public int getPlayerCount() { ++ return this.playerCount.get(); ++ } ++ ++ public int getChunkCount() { ++ return this.chunkCount.get(); ++ } ++ ++ void updateFrom(final RegionizedWorldData data) { ++ this.entityCount.setRelease(data == null ? 0 : data.getEntityCount()); ++ this.playerCount.setRelease(data == null ? 0 : data.getPlayerCount()); ++ this.chunkCount.setRelease(data == null ? 0 : data.getChunkCount()); ++ } ++ ++ static void updateCurrentRegion() { ++ TickRegionScheduler.getCurrentRegion().getData().getRegionStats().updateFrom(TickRegionScheduler.getCurrentRegionizedWorldData()); ++ } ++ } ++ + public static final class TickRegionData implements ThreadedRegionizer.ThreadedRegionData { + + private static final AtomicLong ID_GENERATOR = new AtomicLong(); @@ -8335,10 +8409,18 @@ index 0000000000000000000000000000000000000000..1cc7c32690ba7f7d7cdcbe239314f30f + // chunk holder manager data + private final ChunkHolderManager.HolderManagerRegionData holderManagerRegionData = new ChunkHolderManager.HolderManagerRegionData(); + ++ // async-safe read-only region data ++ private final RegionStats regionStats; ++ + private TickRegionData(final ThreadedRegionizer.ThreadedRegion region) { + this.region = region; + this.world = region.regioniser.world; + this.taskQueueData = new RegionizedTaskQueue.RegionTaskQueueData(this.world.taskQueueRegionData); ++ this.regionStats = new RegionStats(); ++ } ++ ++ public RegionStats getRegionStats() { ++ return this.regionStats; + } + + public RegionizedTaskQueue.RegionTaskQueueData getTaskQueueData() { @@ -8359,6 +8441,10 @@ index 0000000000000000000000000000000000000000..1cc7c32690ba7f7d7cdcbe239314f30f + return this.holderManagerRegionData; + } + ++ T getRegionizedData(final RegionizedData regionizedData) { ++ return (T)this.regionizedData.get(regionizedData); ++ } ++ + T getOrCreateRegionizedData(final RegionizedData regionizedData) { + T ret = (T)this.regionizedData.get(regionizedData); + @@ -8575,13 +8661,14 @@ index 0000000000000000000000000000000000000000..1cc7c32690ba7f7d7cdcbe239314f30f +} diff --git a/src/main/java/io/papermc/paper/threadedregions/commands/CommandServerHealth.java b/src/main/java/io/papermc/paper/threadedregions/commands/CommandServerHealth.java new file mode 100644 -index 0000000000000000000000000000000000000000..1fc6814f48596169db00fdee480f5059abeb23db +index 0000000000000000000000000000000000000000..3bcb1dc98c61e025874cc9e008faa722581a530c --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/commands/CommandServerHealth.java -@@ -0,0 +1,330 @@ +@@ -0,0 +1,355 @@ +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; @@ -8612,8 +8699,15 @@ index 0000000000000000000000000000000000000000..1fc6814f48596169db00fdee480f5059 + +public final class CommandServerHealth extends Command { + -+ private static final DecimalFormat TWO_DECIMAL_PLACES = new DecimalFormat("#0.00"); -+ private static final DecimalFormat ONE_DECIMAL_PLACES = new DecimalFormat("#0.0"); ++ private static final ThreadLocal TWO_DECIMAL_PLACES = ThreadLocal.withInitial(() -> { ++ return new DecimalFormat("#,##0.00"); ++ }); ++ private static final ThreadLocal ONE_DECIMAL_PLACES = ThreadLocal.withInitial(() -> { ++ return new DecimalFormat("#,##0.0"); ++ }); ++ private static final ThreadLocal NO_DECIMAL_PLACES = ThreadLocal.withInitial(() -> { ++ return new DecimalFormat("#,##0"); ++ }); + + private static final TextColor HEADER = TextColor.color(79, 164, 240); + private static final TextColor PRIMARY = TextColor.color(48, 145, 237); @@ -8632,15 +8726,26 @@ index 0000000000000000000000000000000000000000..1fc6814f48596169db00fdee480f5059 + final boolean newline) { + return Component.text() + .append(Component.text(prefix, PRIMARY, TextDecoration.BOLD)) -+ .append(Component.text(ONE_DECIMAL_PLACES.format(util * 100.0), CommandUtil.getUtilisationColourRegion(util))) ++ .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.format(mspt), CommandUtil.getColourForMSPT(mspt))) ++ .append(Component.text(TWO_DECIMAL_PLACES.get().format(mspt), CommandUtil.getColourForMSPT(mspt))) + .append(Component.text(" MSPT at ", PRIMARY)) -+ .append(Component.text(TWO_DECIMAL_PLACES.format(tps), CommandUtil.getColourForTPS(tps))) ++ .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(); ++ } ++ + private static boolean executeRegion(final CommandSender sender, final String commandLabel, final String[] args) { + final ThreadedRegionizer.ThreadedRegion region = + TickRegionScheduler.getCurrentRegion(); @@ -8679,7 +8784,10 @@ index 0000000000000000000000000000000000000000..1fc6814f48596169db00fdee480f5059 + formatRegionInfo("15s: ", util15s, mspt15s, tps15s, true) + ) + .append( -+ formatRegionInfo("1m: ", util1m, mspt1m, tps1m, false) ++ formatRegionInfo("1m: ", util1m, mspt1m, tps1m, true) ++ ) ++ .append( ++ formatRegionStats(region.getData().getRegionStats(), false) + ) + + .build(); @@ -8807,12 +8915,15 @@ index 0000000000000000000000000000000000000000..1fc6814f48596169db00fdee480f5059 + .append(Component.text(":\n", PRIMARY)) + + .append(Component.text(" ", PRIMARY)) -+ .append(Component.text(ONE_DECIMAL_PLACES.format(util * 100.0), CommandUtil.getUtilisationColourRegion(util))) ++ .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.format(mspt), CommandUtil.getColourForMSPT(mspt))) ++ .append(Component.text(TWO_DECIMAL_PLACES.get().format(mspt), CommandUtil.getColourForMSPT(mspt))) + .append(Component.text(" MSPT at ", PRIMARY)) -+ .append(Component.text(TWO_DECIMAL_PLACES.format(tps), CommandUtil.getColourForTPS(tps))) -+ .append(Component.text(" TPS" + ((i + 1) == len ? "" : "\n"), 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() + + .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey().toString() + " run tp " + centerBlockX + ".5 " + yLoc + " " + centerBlockZ + ".5")) @@ -8835,23 +8946,23 @@ index 0000000000000000000000000000000000000000..1fc6814f48596169db00fdee480f5059 + + .append(Component.text(" - ", LIST, TextDecoration.BOLD)) + .append(Component.text("Utilisation: ", PRIMARY)) -+ .append(Component.text(ONE_DECIMAL_PLACES.format(totalUtil * 100.0), CommandUtil.getUtilisationColourRegion(totalUtil / (double)maxThreadCount))) ++ .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.format(maxThreadCount * 100.0), INFORMATION)) ++ .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("Lowest Region TPS: ", PRIMARY)) -+ .append(Component.text(TWO_DECIMAL_PLACES.format(minTps) + "\n", CommandUtil.getColourForTPS(minTps))) ++ .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.format(medianTps) + "\n", CommandUtil.getColourForTPS(medianTps))) ++ .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.format(maxTps) + "\n", CommandUtil.getColourForTPS(maxTps))) ++ .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)) diff --git a/patches/server/0009-Add-chunk-system-throughput-counters-to-tps.patch b/patches/server/0009-Add-chunk-system-throughput-counters-to-tps.patch index 65f58c4..097982e 100644 --- a/patches/server/0009-Add-chunk-system-throughput-counters-to-tps.patch +++ b/patches/server/0009-Add-chunk-system-throughput-counters-to-tps.patch @@ -58,10 +58,10 @@ index 300700477ee34bc22b31315825c0e40f61070cd5..0b78d1eb90500e0123b7281d722805dc chunk = wrappedFull.getWrapped(); } else { diff --git a/src/main/java/io/papermc/paper/threadedregions/commands/CommandServerHealth.java b/src/main/java/io/papermc/paper/threadedregions/commands/CommandServerHealth.java -index 1fc6814f48596169db00fdee480f5059abeb23db..dfab3a36810545933c295a225ef48e6f4793418a 100644 +index 3bcb1dc98c61e025874cc9e008faa722581a530c..0b48f45760829f1f4813b5f0f23e920dca7b1c45 100644 --- a/src/main/java/io/papermc/paper/threadedregions/commands/CommandServerHealth.java +++ b/src/main/java/io/papermc/paper/threadedregions/commands/CommandServerHealth.java -@@ -148,6 +148,9 @@ public final class CommandServerHealth extends Command { +@@ -170,6 +170,9 @@ public final class CommandServerHealth extends Command { totalUtil += (report == null ? 0.0 : report.utilisation()); } @@ -71,16 +71,16 @@ index 1fc6814f48596169db00fdee480f5059abeb23db..dfab3a36810545933c295a225ef48e6f totalUtil += globalTickReport.utilisation(); tpsByRegion.sort(null); -@@ -259,6 +262,12 @@ public final class CommandServerHealth extends Command { - .append(Component.text(ONE_DECIMAL_PLACES.format(maxThreadCount * 100.0), INFORMATION)) +@@ -284,6 +287,12 @@ public final class CommandServerHealth extends Command { + .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.format(loadRate) + ", ", INFORMATION)) ++ .append(Component.text(TWO_DECIMAL_PLACES.get().format(loadRate) + ", ", INFORMATION)) + .append(Component.text("Gen rate: ", PRIMARY)) -+ .append(Component.text(TWO_DECIMAL_PLACES.format(genRate) + "\n", INFORMATION)) ++ .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.format(minTps) + "\n", CommandUtil.getColourForTPS(minTps))) + .append(Component.text(TWO_DECIMAL_PLACES.get().format(minTps) + "\n", CommandUtil.getColourForTPS(minTps)))