From bfbbcb3e68e019da4657ef0da22b889de656ae3f Mon Sep 17 00:00:00 2001 From: Luck Date: Tue, 28 Dec 2021 18:12:33 +0000 Subject: Include platform and system statistics in profiler viewer payload --- .../spark/common/command/modules/HealthModule.java | 35 ++++----- .../common/command/modules/HeapAnalysisModule.java | 85 ++-------------------- .../common/command/modules/SamplerModule.java | 4 +- 3 files changed, 23 insertions(+), 101 deletions(-) (limited to 'spark-common/src/main/java/me/lucko/spark/common/command') diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java index 51fa905..b036d21 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java @@ -30,6 +30,7 @@ import me.lucko.spark.common.command.CommandResponseHandler; import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.monitor.cpu.CpuMonitor; +import me.lucko.spark.common.monitor.disk.DiskUsage; import me.lucko.spark.common.monitor.tick.TickStatistics; import me.lucko.spark.common.util.FormatUtil; import me.lucko.spark.common.util.RollingAverage; @@ -38,15 +39,11 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.TextColor; -import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryType; import java.lang.management.MemoryUsage; -import java.nio.file.FileStore; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.LinkedList; import java.util.List; import java.util.function.Consumer; @@ -65,8 +62,6 @@ import static net.kyori.adventure.text.format.TextDecoration.BOLD; public class HealthModule implements CommandModule { - private static final double MSPT_95_PERCENTILE = 0.95d; - @Override public void registerCommands(Consumer consumer) { consumer.accept(Command.builder() @@ -150,11 +145,7 @@ public class HealthModule implements CommandModule { addDetailedMemoryStats(report, memoryMXBean); } - try { - addDiskStats(report); - } catch (IOException e) { - e.printStackTrace(); - } + addDiskStats(report); resp.reply(report); } @@ -309,10 +300,14 @@ public class HealthModule implements CommandModule { } } - private static void addDiskStats(List report) throws IOException { - FileStore fileStore = Files.getFileStore(Paths.get(".")); - long totalSpace = fileStore.getTotalSpace(); - long usedSpace = totalSpace - fileStore.getUsableSpace(); + private static void addDiskStats(List report) { + long total = DiskUsage.getTotal(); + long used = DiskUsage.getUsed(); + + if (total == 0 || used == 0) { + return; + } + report.add(text() .append(text(">", DARK_GRAY, BOLD)) .append(space()) @@ -321,18 +316,18 @@ public class HealthModule implements CommandModule { ); report.add(text() .content(" ") - .append(text(FormatUtil.formatBytes(usedSpace), WHITE)) + .append(text(FormatUtil.formatBytes(used), WHITE)) .append(space()) .append(text("/", GRAY)) .append(space()) - .append(text(FormatUtil.formatBytes(totalSpace), WHITE)) + .append(text(FormatUtil.formatBytes(total), WHITE)) .append(text(" ")) .append(text("(", GRAY)) - .append(text(FormatUtil.percent(usedSpace, totalSpace), GREEN)) + .append(text(FormatUtil.percent(used, total), GREEN)) .append(text(")", GRAY)) .build() ); - report.add(text().content(" ").append(generateDiskUsageDiagram(usedSpace, totalSpace, 40)).build()); + report.add(text().content(" ").append(generateDiskUsageDiagram(used, total, 40)).build()); report.add(empty()); } @@ -355,7 +350,7 @@ public class HealthModule implements CommandModule { .append(text('/', GRAY)) .append(formatTickDuration(average.median())) .append(text('/', GRAY)) - .append(formatTickDuration(average.percentile(MSPT_95_PERCENTILE))) + .append(formatTickDuration(average.percentile95th())) .append(text('/', GRAY)) .append(formatTickDuration(average.max())) .build(); diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java index 70f6c3c..491ec1e 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java @@ -30,20 +30,15 @@ import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.heapdump.HeapDump; import me.lucko.spark.common.heapdump.HeapDumpSummary; +import me.lucko.spark.common.util.Compression; import me.lucko.spark.common.util.FormatUtil; -import me.lucko.spark.proto.SparkProtos; +import me.lucko.spark.proto.SparkHeapProtos; import net.kyori.adventure.text.event.ClickEvent; -import org.tukaani.xz.LZMA2Options; -import org.tukaani.xz.LZMAOutputStream; -import org.tukaani.xz.XZOutputStream; - import okhttp3.MediaType; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Iterator; @@ -51,7 +46,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.LongConsumer; -import java.util.zip.GZIPOutputStream; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.format.NamedTextColor.GOLD; @@ -98,7 +92,7 @@ public class HeapAnalysisModule implements CommandModule { return; } - SparkProtos.HeapData output = heapDump.toProto(platform.getPlugin().getPlatformInfo(), sender); + SparkHeapProtos.HeapData output = heapDump.toProto(platform, sender); boolean saveToFile = false; if (arguments.boolFlag("save-to-file")) { @@ -175,11 +169,11 @@ public class HeapAnalysisModule implements CommandModule { platform.getActivityLog().addToLog(Activity.fileActivity(sender, System.currentTimeMillis(), "Heap dump", file.toString())); - CompressionMethod compressionMethod = null; + Compression compressionMethod = null; Iterator compressArgs = arguments.stringFlag("compress").iterator(); if (compressArgs.hasNext()) { try { - compressionMethod = CompressionMethod.valueOf(compressArgs.next().toUpperCase()); + compressionMethod = Compression.valueOf(compressArgs.next().toUpperCase()); } catch (IllegalArgumentException e) { // ignore } @@ -194,7 +188,7 @@ public class HeapAnalysisModule implements CommandModule { } } - private static void heapDumpCompress(SparkPlatform platform, CommandResponseHandler resp, Path file, CompressionMethod method) throws IOException { + private static void heapDumpCompress(SparkPlatform platform, CommandResponseHandler resp, Path file, Compression method) throws IOException { resp.broadcastPrefixed(text("Compressing heap dump, please wait...")); long size = Files.size(file); @@ -244,71 +238,4 @@ public class HeapAnalysisModule implements CommandModule { ); } - public enum CompressionMethod { - GZIP { - @Override - public Path compress(Path file, LongConsumer progressHandler) throws IOException { - Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".gz"); - try (InputStream in = Files.newInputStream(file)) { - try (OutputStream out = Files.newOutputStream(compressedFile)) { - try (GZIPOutputStream compressionOut = new GZIPOutputStream(out, 1024 * 64)) { - copy(in, compressionOut, progressHandler); - } - } - } - return compressedFile; - } - }, - XZ { - @Override - public Path compress(Path file, LongConsumer progressHandler) throws IOException { - Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".xz"); - try (InputStream in = Files.newInputStream(file)) { - try (OutputStream out = Files.newOutputStream(compressedFile)) { - try (XZOutputStream compressionOut = new XZOutputStream(out, new LZMA2Options())) { - copy(in, compressionOut, progressHandler); - } - } - } - return compressedFile; - } - }, - LZMA { - @Override - public Path compress(Path file, LongConsumer progressHandler) throws IOException { - Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".lzma"); - try (InputStream in = Files.newInputStream(file)) { - try (OutputStream out = Files.newOutputStream(compressedFile)) { - try (LZMAOutputStream compressionOut = new LZMAOutputStream(out, new LZMA2Options(), true)) { - copy(in, compressionOut, progressHandler); - } - } - } - return compressedFile; - } - }; - - public abstract Path compress(Path file, LongConsumer progressHandler) throws IOException; - - private static long copy(InputStream from, OutputStream to, LongConsumer progress) throws IOException { - byte[] buf = new byte[1024 * 64]; - long total = 0; - long iterations = 0; - while (true) { - int r = from.read(buf); - if (r == -1) { - break; - } - to.write(buf, 0, r); - total += r; - - // report progress every 5MB - if (iterations++ % ((1024 / 64) * 5) == 0) { - progress.accept(total); - } - } - return total; - } - } - } diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java index 2dd07c9..26f20e7 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java @@ -40,7 +40,7 @@ import me.lucko.spark.common.sampler.async.AsyncSampler; import me.lucko.spark.common.sampler.node.MergeMode; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.util.MethodDisambiguator; -import me.lucko.spark.proto.SparkProtos; +import me.lucko.spark.proto.SparkSamplerProtos; import net.kyori.adventure.text.event.ClickEvent; @@ -305,7 +305,7 @@ public class SamplerModule implements CommandModule { } private void handleUpload(SparkPlatform platform, CommandResponseHandler resp, Sampler sampler, ThreadNodeOrder threadOrder, String comment, MergeMode mergeMode, boolean saveToFileFlag) { - SparkProtos.SamplerData output = sampler.toProto(platform.getPlugin().getPlatformInfo(), resp.sender(), threadOrder, comment, mergeMode, platform.createClassSourceLookup()); + SparkSamplerProtos.SamplerData output = sampler.toProto(platform, resp.sender(), threadOrder, comment, mergeMode, platform.createClassSourceLookup()); boolean saveToFile = false; if (saveToFileFlag) { -- cgit From d2716da1dc7f61aa45c0058e9a8fd65aa858f3c8 Mon Sep 17 00:00:00 2001 From: Luck Date: Thu, 20 Jan 2022 20:22:02 +0000 Subject: Add ping statistics and command --- .../spark/bukkit/BukkitPlayerPingProvider.java | 57 +++++ .../me/lucko/spark/bukkit/BukkitSparkPlugin.java | 10 + .../placeholder/SparkPlaceholderProvider.java | 54 ++--- .../bungeecord/BungeeCordPlayerPingProvider.java | 47 ++++ .../spark/bungeecord/BungeeCordSparkPlugin.java | 6 + .../java/me/lucko/spark/common/SparkPlatform.java | 17 ++ .../java/me/lucko/spark/common/SparkPlugin.java | 12 ++ .../spark/common/command/modules/HealthModule.java | 239 ++++++++------------- .../spark/common/monitor/MonitoringExecutor.java | 16 ++ .../lucko/spark/common/monitor/cpu/CpuMonitor.java | 12 +- .../spark/common/monitor/ping/PingStatistics.java | 148 +++++++++++++ .../spark/common/monitor/ping/PingSummary.java | 81 +++++++ .../common/monitor/ping/PlayerPingProvider.java | 40 ++++ .../platform/PlatformStatisticsProvider.java | 17 +- .../me/lucko/spark/common/util/RollingAverage.java | 6 + .../spark/common/util/StatisticFormatter.java | 187 ++++++++++++++++ spark-common/src/main/proto/spark/spark.proto | 25 ++- .../spark/fabric/FabricPlayerPingProvider.java | 47 ++++ .../placeholder/SparkFabricPlaceholderApi.java | 36 ++-- .../fabric/plugin/FabricServerSparkPlugin.java | 7 + .../lucko/spark/forge/ForgePlayerPingProvider.java | 47 ++++ .../spark/forge/plugin/ForgeServerSparkPlugin.java | 7 + .../spark/nukkit/NukkitPlayerPingProvider.java | 47 ++++ .../me/lucko/spark/nukkit/NukkitSparkPlugin.java | 6 + .../spark/sponge/Sponge7PlayerPingProvider.java | 47 ++++ .../me/lucko/spark/sponge/Sponge7SparkPlugin.java | 10 + .../spark/sponge/Sponge8PlayerPingProvider.java | 47 ++++ .../me/lucko/spark/sponge/Sponge8SparkPlugin.java | 10 + .../spark/velocity/VelocityPlayerPingProvider.java | 46 ++++ .../lucko/spark/velocity/VelocitySparkPlugin.java | 6 + .../velocity/Velocity4PlayerPingProvider.java | 46 ++++ .../lucko/spark/velocity/Velocity4SparkPlugin.java | 6 + .../spark/waterdog/WaterdogPlayerPingProvider.java | 47 ++++ .../lucko/spark/waterdog/WaterdogSparkPlugin.java | 6 + 34 files changed, 1220 insertions(+), 222 deletions(-) create mode 100644 spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitPlayerPingProvider.java create mode 100644 spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordPlayerPingProvider.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/monitor/MonitoringExecutor.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingSummary.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PlayerPingProvider.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java create mode 100644 spark-fabric/src/main/java/me/lucko/spark/fabric/FabricPlayerPingProvider.java create mode 100644 spark-forge/src/main/java/me/lucko/spark/forge/ForgePlayerPingProvider.java create mode 100644 spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitPlayerPingProvider.java create mode 100644 spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7PlayerPingProvider.java create mode 100644 spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8PlayerPingProvider.java create mode 100644 spark-velocity/src/main/java/me/lucko/spark/velocity/VelocityPlayerPingProvider.java create mode 100644 spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4PlayerPingProvider.java create mode 100644 spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogPlayerPingProvider.java (limited to 'spark-common/src/main/java/me/lucko/spark/common/command') diff --git a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitPlayerPingProvider.java b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitPlayerPingProvider.java new file mode 100644 index 0000000..2cf58cf --- /dev/null +++ b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitPlayerPingProvider.java @@ -0,0 +1,57 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.bukkit; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; + +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import java.util.Map; + +public class BukkitPlayerPingProvider implements PlayerPingProvider { + + public static boolean isSupported() { + try { + Player.Spigot.class.getMethod("getPing"); + return true; + } catch (Exception e) { + return false; + } + } + + private final Server server; + + public BukkitPlayerPingProvider(Server server) { + this.server = server; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Player player : this.server.getOnlinePlayers()) { + builder.put(player.getName(), player.spigot().getPing()); + } + return builder.build(); + } +} diff --git a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitSparkPlugin.java b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitSparkPlugin.java index f81a176..6929a4d 100644 --- a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitSparkPlugin.java +++ b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitSparkPlugin.java @@ -25,6 +25,7 @@ import me.lucko.spark.bukkit.placeholder.SparkMVdWPlaceholders; import me.lucko.spark.bukkit.placeholder.SparkPlaceholderApi; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; @@ -171,6 +172,15 @@ public class BukkitSparkPlugin extends JavaPlugin implements SparkPlugin { return new BukkitClassSourceLookup(); } + @Override + public PlayerPingProvider createPlayerPingProvider() { + if (BukkitPlayerPingProvider.isSupported()) { + return new BukkitPlayerPingProvider(getServer()); + } else { + return null; + } + } + @Override public PlatformInfo getPlatformInfo() { return new BukkitPlatformInfo(getServer()); diff --git a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/placeholder/SparkPlaceholderProvider.java b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/placeholder/SparkPlaceholderProvider.java index 6e14bdb..96c9e93 100644 --- a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/placeholder/SparkPlaceholderProvider.java +++ b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/placeholder/SparkPlaceholderProvider.java @@ -21,9 +21,9 @@ package me.lucko.spark.bukkit.placeholder; import me.lucko.spark.common.SparkPlatform; -import me.lucko.spark.common.command.modules.HealthModule; import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.tick.TickStatistics; +import me.lucko.spark.common.util.StatisticFormatter; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; @@ -42,22 +42,22 @@ enum SparkPlaceholderProvider { switch (placeholder) { case "tps": return Component.text() - .append(HealthModule.formatTps(tickStatistics.tps5Sec())).append(Component.text(", ")) - .append(HealthModule.formatTps(tickStatistics.tps10Sec())).append(Component.text(", ")) - .append(HealthModule.formatTps(tickStatistics.tps1Min())).append(Component.text(", ")) - .append(HealthModule.formatTps(tickStatistics.tps5Min())).append(Component.text(", ")) - .append(HealthModule.formatTps(tickStatistics.tps15Min())) + .append(StatisticFormatter.formatTps(tickStatistics.tps5Sec())).append(Component.text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps10Sec())).append(Component.text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps1Min())).append(Component.text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps5Min())).append(Component.text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps15Min())) .build(); case "tps_5s": - return HealthModule.formatTps(tickStatistics.tps5Sec()); + return StatisticFormatter.formatTps(tickStatistics.tps5Sec()); case "tps_10s": - return HealthModule.formatTps(tickStatistics.tps10Sec()); + return StatisticFormatter.formatTps(tickStatistics.tps10Sec()); case "tps_1m": - return HealthModule.formatTps(tickStatistics.tps1Min()); + return StatisticFormatter.formatTps(tickStatistics.tps1Min()); case "tps_5m": - return HealthModule.formatTps(tickStatistics.tps5Min()); + return StatisticFormatter.formatTps(tickStatistics.tps5Min()); case "tps_15m": - return HealthModule.formatTps(tickStatistics.tps15Min()); + return StatisticFormatter.formatTps(tickStatistics.tps15Min()); } } @@ -70,13 +70,13 @@ enum SparkPlaceholderProvider { switch (placeholder) { case "tickduration": return Component.text() - .append(HealthModule.formatTickDurations(tickStatistics.duration10Sec())).append(Component.text("; ")) - .append(HealthModule.formatTickDurations(tickStatistics.duration1Min())) + .append(StatisticFormatter.formatTickDurations(tickStatistics.duration10Sec())).append(Component.text("; ")) + .append(StatisticFormatter.formatTickDurations(tickStatistics.duration1Min())) .build(); case "tickduration_10s": - return HealthModule.formatTickDurations(tickStatistics.duration10Sec()); + return StatisticFormatter.formatTickDurations(tickStatistics.duration10Sec()); case "tickduration_1m": - return HealthModule.formatTickDurations(tickStatistics.duration1Min()); + return StatisticFormatter.formatTickDurations(tickStatistics.duration1Min()); } } @@ -84,28 +84,28 @@ enum SparkPlaceholderProvider { switch (placeholder) { case "cpu_system": return Component.text() - .append(HealthModule.formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(Component.text(", ")) - .append(HealthModule.formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(Component.text(", ")) - .append(HealthModule.formatCpuUsage(CpuMonitor.systemLoad15MinAvg())) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(Component.text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(Component.text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad15MinAvg())) .build(); case "cpu_system_10s": - return HealthModule.formatCpuUsage(CpuMonitor.systemLoad10SecAvg()); + return StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad10SecAvg()); case "cpu_system_1m": - return HealthModule.formatCpuUsage(CpuMonitor.systemLoad1MinAvg()); + return StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad1MinAvg()); case "cpu_system_15m": - return HealthModule.formatCpuUsage(CpuMonitor.systemLoad15MinAvg()); + return StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad15MinAvg()); case "cpu_process": return Component.text() - .append(HealthModule.formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(Component.text(", ")) - .append(HealthModule.formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(Component.text(", ")) - .append(HealthModule.formatCpuUsage(CpuMonitor.processLoad15MinAvg())) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(Component.text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(Component.text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad15MinAvg())) .build(); case "cpu_process_10s": - return HealthModule.formatCpuUsage(CpuMonitor.processLoad10SecAvg()); + return StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad10SecAvg()); case "cpu_process_1m": - return HealthModule.formatCpuUsage(CpuMonitor.processLoad1MinAvg()); + return StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad1MinAvg()); case "cpu_process_15m": - return HealthModule.formatCpuUsage(CpuMonitor.processLoad15MinAvg()); + return StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad15MinAvg()); } } diff --git a/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordPlayerPingProvider.java b/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordPlayerPingProvider.java new file mode 100644 index 0000000..37955a3 --- /dev/null +++ b/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordPlayerPingProvider.java @@ -0,0 +1,47 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.bungeecord; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; + +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +import java.util.Map; + +public class BungeeCordPlayerPingProvider implements PlayerPingProvider { + private final ProxyServer proxy; + + public BungeeCordPlayerPingProvider(ProxyServer proxy) { + this.proxy = proxy; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (ProxiedPlayer player : this.proxy.getPlayers()) { + builder.put(player.getName(), player.getPing()); + } + return builder.build(); + } +} diff --git a/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordSparkPlugin.java b/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordSparkPlugin.java index ebdfe18..e259adc 100644 --- a/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordSparkPlugin.java +++ b/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordSparkPlugin.java @@ -22,6 +22,7 @@ package me.lucko.spark.bungeecord; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.util.ClassSourceLookup; @@ -90,6 +91,11 @@ public class BungeeCordSparkPlugin extends Plugin implements SparkPlugin { return new BungeeCordClassSourceLookup(); } + @Override + public PlayerPingProvider createPlayerPingProvider() { + return new BungeeCordPlayerPingProvider(getProxy()); + } + @Override public PlatformInfo getPlatformInfo() { return new BungeeCordPlatformInfo(getProxy()); diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java index 53454aa..c93b876 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java @@ -40,6 +40,8 @@ import me.lucko.spark.common.command.tabcomplete.CompletionSupplier; import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.memory.GarbageCollectorStatistics; +import me.lucko.spark.common.monitor.ping.PingStatistics; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.monitor.tick.TickStatistics; import me.lucko.spark.common.platform.PlatformStatisticsProvider; import me.lucko.spark.common.tick.TickHook; @@ -99,6 +101,7 @@ public class SparkPlatform { private final TickHook tickHook; private final TickReporter tickReporter; private final TickStatistics tickStatistics; + private final PingStatistics pingStatistics; private final PlatformStatisticsProvider statisticsProvider; private Map startupGcStatistics = ImmutableMap.of(); private long serverNormalOperationStartTime; @@ -136,6 +139,10 @@ public class SparkPlatform { this.tickHook = plugin.createTickHook(); this.tickReporter = plugin.createTickReporter(); this.tickStatistics = this.tickHook != null ? new TickStatistics() : null; + + PlayerPingProvider pingProvider = plugin.createPlayerPingProvider(); + this.pingStatistics = pingProvider != null ? new PingStatistics(pingProvider) : null; + this.statisticsProvider = new PlatformStatisticsProvider(this); } @@ -152,6 +159,9 @@ public class SparkPlatform { this.tickReporter.addCallback(this.tickStatistics); this.tickReporter.start(); } + if (this.pingStatistics != null) { + this.pingStatistics.start(); + } CpuMonitor.ensureMonitoring(); // poll startup GC statistics after plugins & the world have loaded @@ -172,6 +182,9 @@ public class SparkPlatform { if (this.tickReporter != null) { this.tickReporter.close(); } + if (this.pingStatistics != null) { + this.pingStatistics.close(); + } for (CommandModule module : this.commandModules) { module.close(); @@ -231,6 +244,10 @@ public class SparkPlatform { return this.tickStatistics; } + public PingStatistics getPingStatistics() { + return this.pingStatistics; + } + public Map getStartupGcStatistics() { return this.startupGcStatistics; } diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java index f312916..5feb172 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java @@ -22,6 +22,7 @@ package me.lucko.spark.common; import me.lucko.spark.api.Spark; import me.lucko.spark.common.command.sender.CommandSender; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; @@ -120,6 +121,17 @@ public interface SparkPlugin { return ClassSourceLookup.NO_OP; } + /** + * Creates a player ping provider function. + * + *

Returns {@code null} if the platform does not support querying player pings

+ * + * @return the player ping provider function + */ + default PlayerPingProvider createPlayerPingProvider() { + return null; + } + /** * Gets information for the platform. * diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java index b036d21..ea4f140 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java @@ -20,8 +20,6 @@ package me.lucko.spark.common.command.modules; -import com.google.common.base.Strings; - import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.command.Arguments; import me.lucko.spark.common.command.Command; @@ -31,13 +29,14 @@ import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.disk.DiskUsage; +import me.lucko.spark.common.monitor.ping.PingStatistics; +import me.lucko.spark.common.monitor.ping.PingSummary; import me.lucko.spark.common.monitor.tick.TickStatistics; import me.lucko.spark.common.util.FormatUtil; import me.lucko.spark.common.util.RollingAverage; +import me.lucko.spark.common.util.StatisticFormatter; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.format.TextColor; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; @@ -46,6 +45,7 @@ import java.lang.management.MemoryType; import java.lang.management.MemoryUsage; import java.util.LinkedList; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import static net.kyori.adventure.text.Component.empty; @@ -57,7 +57,6 @@ import static net.kyori.adventure.text.format.NamedTextColor.GRAY; import static net.kyori.adventure.text.format.NamedTextColor.GREEN; import static net.kyori.adventure.text.format.NamedTextColor.RED; import static net.kyori.adventure.text.format.NamedTextColor.WHITE; -import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; import static net.kyori.adventure.text.format.TextDecoration.BOLD; public class HealthModule implements CommandModule { @@ -71,6 +70,14 @@ public class HealthModule implements CommandModule { .build() ); + consumer.accept(Command.builder() + .aliases("ping") + .argumentUsage("player", "username") + .executor(HealthModule::ping) + .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--player")) + .build() + ); + consumer.accept(Command.builder() .aliases("healthreport", "health", "ht") .argumentUsage("memory", null) @@ -86,11 +93,11 @@ public class HealthModule implements CommandModule { resp.replyPrefixed(text("TPS from last 5s, 10s, 1m, 5m, 15m:")); resp.replyPrefixed(text() .content(" ") - .append(formatTps(tickStatistics.tps5Sec())).append(text(", ")) - .append(formatTps(tickStatistics.tps10Sec())).append(text(", ")) - .append(formatTps(tickStatistics.tps1Min())).append(text(", ")) - .append(formatTps(tickStatistics.tps5Min())).append(text(", ")) - .append(formatTps(tickStatistics.tps15Min())) + .append(StatisticFormatter.formatTps(tickStatistics.tps5Sec())).append(text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps10Sec())).append(text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps1Min())).append(text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps5Min())).append(text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps15Min())) .build() ); resp.replyPrefixed(empty()); @@ -99,8 +106,8 @@ public class HealthModule implements CommandModule { resp.replyPrefixed(text("Tick durations (min/med/95%ile/max ms) from last 10s, 1m:")); resp.replyPrefixed(text() .content(" ") - .append(formatTickDurations(tickStatistics.duration10Sec())).append(text("; ")) - .append(formatTickDurations(tickStatistics.duration1Min())) + .append(StatisticFormatter.formatTickDurations(tickStatistics.duration10Sec())).append(text("; ")) + .append(StatisticFormatter.formatTickDurations(tickStatistics.duration1Min())) .build() ); resp.replyPrefixed(empty()); @@ -110,22 +117,67 @@ public class HealthModule implements CommandModule { resp.replyPrefixed(text("CPU usage from last 10s, 1m, 15m:")); resp.replyPrefixed(text() .content(" ") - .append(formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(text(", ")) - .append(formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(text(", ")) - .append(formatCpuUsage(CpuMonitor.systemLoad15MinAvg())) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad15MinAvg())) .append(text(" (system)", DARK_GRAY)) .build() ); resp.replyPrefixed(text() .content(" ") - .append(formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(text(", ")) - .append(formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(text(", ")) - .append(formatCpuUsage(CpuMonitor.processLoad15MinAvg())) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad15MinAvg())) .append(text(" (process)", DARK_GRAY)) .build() ); } + private static void ping(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) { + PingStatistics pingStatistics = platform.getPingStatistics(); + if (pingStatistics == null) { + resp.replyPrefixed(text("Ping data is not available on this platform.")); + return; + } + + // lookup for specific player + Set players = arguments.stringFlag("player"); + if (!players.isEmpty()) { + for (String player : players) { + PingStatistics.PlayerPing playerPing = pingStatistics.query(player); + if (playerPing == null) { + resp.replyPrefixed(text("Ping data is not available for '" + player + "'.")); + } else { + resp.replyPrefixed(text() + .content("Player ") + .append(text(playerPing.name(), WHITE)) + .append(text(" has ")) + .append(StatisticFormatter.formatPingRtt(playerPing.ping())) + .append(text(" ms ping.")) + .build() + ); + } + } + return; + } + + PingSummary summary = pingStatistics.currentSummary(); + RollingAverage average = pingStatistics.getPingAverage(); + + if (summary.total() == 0 && average.getSamples() == 0) { + resp.replyPrefixed(text("There is not enough data to show ping averages yet. Please try again later.")); + return; + } + + resp.replyPrefixed(text("Average Pings (min/med/95%ile/max ms) from now, last 15m:")); + resp.replyPrefixed(text() + .content(" ") + .append(StatisticFormatter.formatPingRtts(summary.min(), summary.median(), summary.percentile95th(), summary.max())).append(text("; ")) + .append(StatisticFormatter.formatPingRtts(average.min(), average.median(), average.percentile95th(), average.max())) + .build() + ); + } + private static void healthReport(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) { resp.replyPrefixed(text("Generating server health report...")); List report = new LinkedList<>(); @@ -159,11 +211,11 @@ public class HealthModule implements CommandModule { ); report.add(text() .content(" ") - .append(formatTps(tickStatistics.tps5Sec())).append(text(", ")) - .append(formatTps(tickStatistics.tps10Sec())).append(text(", ")) - .append(formatTps(tickStatistics.tps1Min())).append(text(", ")) - .append(formatTps(tickStatistics.tps5Min())).append(text(", ")) - .append(formatTps(tickStatistics.tps15Min())) + .append(StatisticFormatter.formatTps(tickStatistics.tps5Sec())).append(text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps10Sec())).append(text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps1Min())).append(text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps5Min())).append(text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps15Min())) .build() ); report.add(empty()); @@ -177,8 +229,8 @@ public class HealthModule implements CommandModule { ); report.add(text() .content(" ") - .append(formatTickDurations(tickStatistics.duration10Sec())).append(text("; ")) - .append(formatTickDurations(tickStatistics.duration1Min())) + .append(StatisticFormatter.formatTickDurations(tickStatistics.duration10Sec())).append(text("; ")) + .append(StatisticFormatter.formatTickDurations(tickStatistics.duration1Min())) .build() ); report.add(empty()); @@ -194,17 +246,17 @@ public class HealthModule implements CommandModule { ); report.add(text() .content(" ") - .append(formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(text(", ")) - .append(formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(text(", ")) - .append(formatCpuUsage(CpuMonitor.systemLoad15MinAvg())) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad15MinAvg())) .append(text(" (system)", DARK_GRAY)) .build() ); report.add(text() .content(" ") - .append(formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(text(", ")) - .append(formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(text(", ")) - .append(formatCpuUsage(CpuMonitor.processLoad15MinAvg())) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad15MinAvg())) .append(text(" (process)", DARK_GRAY)) .build() ); @@ -232,7 +284,7 @@ public class HealthModule implements CommandModule { .append(text(")", GRAY)) .build() ); - report.add(text().content(" ").append(generateMemoryUsageDiagram(heapUsage, 40)).build()); + report.add(text().content(" ").append(StatisticFormatter.generateMemoryUsageDiagram(heapUsage, 60)).build()); report.add(empty()); } @@ -283,7 +335,7 @@ public class HealthModule implements CommandModule { .append(text(")", GRAY)) .build() ); - report.add(text().content(" ").append(generateMemoryPoolDiagram(usage, collectionUsage, 40)).build()); + report.add(text().content(" ").append(StatisticFormatter.generateMemoryPoolDiagram(usage, collectionUsage, 60)).build()); if (collectionUsage != null) { report.add(text() @@ -327,127 +379,8 @@ public class HealthModule implements CommandModule { .append(text(")", GRAY)) .build() ); - report.add(text().content(" ").append(generateDiskUsageDiagram(used, total, 40)).build()); + report.add(text().content(" ").append(StatisticFormatter.generateDiskUsageDiagram(used, total, 60)).build()); report.add(empty()); } - public static TextComponent formatTps(double tps) { - TextColor color; - if (tps > 18.0) { - color = GREEN; - } else if (tps > 16.0) { - color = YELLOW; - } else { - color = RED; - } - - return text((tps > 20.0 ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0), color); - } - - public static TextComponent formatTickDurations(RollingAverage average) { - return text() - .append(formatTickDuration(average.min())) - .append(text('/', GRAY)) - .append(formatTickDuration(average.median())) - .append(text('/', GRAY)) - .append(formatTickDuration(average.percentile95th())) - .append(text('/', GRAY)) - .append(formatTickDuration(average.max())) - .build(); - } - - public static TextComponent formatTickDuration(double duration) { - TextColor color; - if (duration >= 50d) { - color = RED; - } else if (duration >= 40d) { - color = YELLOW; - } else { - color = GREEN; - } - - return text(String.format("%.1f", duration), color); - } - - public static TextComponent formatCpuUsage(double usage) { - TextColor color; - if (usage > 0.9) { - color = RED; - } else if (usage > 0.65) { - color = YELLOW; - } else { - color = GREEN; - } - - return text(FormatUtil.percent(usage, 1d), color); - } - - private static TextComponent generateMemoryUsageDiagram(MemoryUsage usage, int length) { - double used = usage.getUsed(); - double committed = usage.getCommitted(); - double max = usage.getMax(); - - int usedChars = (int) ((used * length) / max); - int committedChars = (int) ((committed * length) / max); - - TextComponent.Builder line = text().content(Strings.repeat("/", usedChars)).color(GRAY); - if (committedChars > usedChars) { - line.append(text(Strings.repeat(" ", (committedChars - usedChars) - 1))); - line.append(text("|", YELLOW)); - } - if (length > committedChars) { - line.append(text(Strings.repeat(" ", (length - committedChars)))); - } - - return text() - .append(text("[", DARK_GRAY)) - .append(line.build()) - .append(text("]", DARK_GRAY)) - .build(); - } - - private static TextComponent generateMemoryPoolDiagram(MemoryUsage usage, MemoryUsage collectionUsage, int length) { - double used = usage.getUsed(); - double collectionUsed = used; - if (collectionUsage != null) { - collectionUsed = collectionUsage.getUsed(); - } - double committed = usage.getCommitted(); - double max = usage.getMax(); - - int usedChars = (int) ((used * length) / max); - int collectionUsedChars = (int) ((collectionUsed * length) / max); - int committedChars = (int) ((committed * length) / max); - - TextComponent.Builder line = text().content(Strings.repeat("/", collectionUsedChars)).color(GRAY); - - if (usedChars > collectionUsedChars) { - line.append(text("|", RED)); - line.append(text(Strings.repeat("/", (usedChars - collectionUsedChars) - 1), GRAY)); - } - if (committedChars > usedChars) { - line.append(text(Strings.repeat(" ", (committedChars - usedChars) - 1))); - line.append(text("|", YELLOW)); - } - if (length > committedChars) { - line.append(text(Strings.repeat(" ", (length - committedChars)))); - } - - return text() - .append(text("[", DARK_GRAY)) - .append(line.build()) - .append(text("]", DARK_GRAY)) - .build(); - } - - private static TextComponent generateDiskUsageDiagram(double used, double max, int length) { - int usedChars = (int) ((used * length) / max); - String line = Strings.repeat("/", usedChars) + Strings.repeat(" ", length - usedChars); - return text() - .append(text("[", DARK_GRAY)) - .append(text(line, GRAY)) - .append(text("]", DARK_GRAY)) - .build(); - } - } diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/MonitoringExecutor.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/MonitoringExecutor.java new file mode 100644 index 0000000..779dbbf --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/MonitoringExecutor.java @@ -0,0 +1,16 @@ +package me.lucko.spark.common.monitor; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +public enum MonitoringExecutor { + ; + + /** The executor used to monitor & calculate rolling averages. */ + public static final ScheduledExecutorService INSTANCE = Executors.newSingleThreadScheduledExecutor(r -> { + Thread thread = Executors.defaultThreadFactory().newThread(r); + thread.setName("spark-monitor"); + thread.setDaemon(true); + return thread; + }); +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/cpu/CpuMonitor.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/cpu/CpuMonitor.java index 43e1f90..b4ab831 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/monitor/cpu/CpuMonitor.java +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/cpu/CpuMonitor.java @@ -20,12 +20,11 @@ package me.lucko.spark.common.monitor.cpu; +import me.lucko.spark.common.monitor.MonitoringExecutor; import me.lucko.spark.common.util.RollingAverage; import java.lang.management.ManagementFactory; import java.math.BigDecimal; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.management.JMX; @@ -42,13 +41,6 @@ public enum CpuMonitor { private static final String OPERATING_SYSTEM_BEAN = "java.lang:type=OperatingSystem"; /** The OperatingSystemMXBean instance */ private static final OperatingSystemMXBean BEAN; - /** The executor used to monitor & calculate rolling averages. */ - private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(r -> { - Thread thread = Executors.defaultThreadFactory().newThread(r); - thread.setName("spark-cpu-monitor"); - thread.setDaemon(true); - return thread; - }); // Rolling averages for system/process data private static final RollingAverage SYSTEM_AVERAGE_10_SEC = new RollingAverage(10); @@ -68,7 +60,7 @@ public enum CpuMonitor { } // schedule rolling average calculations. - EXECUTOR.scheduleAtFixedRate(new RollingAverageCollectionTask(), 1, 1, TimeUnit.SECONDS); + MonitoringExecutor.INSTANCE.scheduleAtFixedRate(new RollingAverageCollectionTask(), 1, 1, TimeUnit.SECONDS); } /** diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java new file mode 100644 index 0000000..8b5b5b3 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java @@ -0,0 +1,148 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.monitor.ping; + +import me.lucko.spark.common.monitor.MonitoringExecutor; +import me.lucko.spark.common.util.RollingAverage; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.math.BigDecimal; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * Provides statistics for player ping RTT to the server. + */ +public final class PingStatistics implements Runnable, AutoCloseable { + private static final int WINDOW_SIZE_SECONDS = (int) TimeUnit.MINUTES.toSeconds(15); + private static final int QUERY_RATE_SECONDS = 10; + + /** The platform function that provides player ping times */ + private final PlayerPingProvider provider; + /** Rolling average of the median ping across all players */ + private final RollingAverage rollingAverage = new RollingAverage(WINDOW_SIZE_SECONDS / QUERY_RATE_SECONDS); + + /** The scheduler task that polls pings and calculates the rolling average */ + private ScheduledFuture future; + + public PingStatistics(PlayerPingProvider provider) { + this.provider = provider; + } + + /** + * Starts the statistics monitor + */ + public void start() { + if (this.future != null) { + throw new IllegalStateException(); + } + this.future = MonitoringExecutor.INSTANCE.scheduleAtFixedRate(this, QUERY_RATE_SECONDS, QUERY_RATE_SECONDS, TimeUnit.SECONDS); + } + + @Override + public void close() { + if (this.future != null) { + this.future.cancel(false); + this.future = null; + } + } + + @Override + public void run() { + PingSummary summary = currentSummary(); + if (summary.total() == 0) { + return; + } + + this.rollingAverage.add(BigDecimal.valueOf(summary.median())); + } + + /** + * Gets the ping rolling average. + * + * @return the rolling average + */ + public RollingAverage getPingAverage() { + return this.rollingAverage; + } + + /** + * Queries a summary of current player pings. + * + * @return a summary of current pings + */ + public PingSummary currentSummary() { + Map results = this.provider.poll(); + int[] values = results.values().stream().filter(ping -> ping > 0).mapToInt(i -> i).toArray(); + return values.length == 0 + ? new PingSummary(new int[]{0}) + : new PingSummary(values); + } + + /** + * Queries the ping of a given player. + * + * @param playerName the name of the player + * @return the ping, if available + */ + public @Nullable PlayerPing query(String playerName) { + Map results = this.provider.poll(); + + // try exact match + Integer result = results.get(playerName); + if (result != null) { + return new PlayerPing(playerName, result); + } + + // try case-insensitive match + for (Map.Entry entry : results.entrySet()) { + if (entry.getKey().equalsIgnoreCase(playerName)) { + return new PlayerPing( + entry.getKey(), + entry.getValue() + ); + } + } + + return null; + } + + public static final class PlayerPing { + private final String name; + private final int ping; + + PlayerPing(String name, int ping) { + this.name = name; + this.ping = ping; + } + + public String name() { + return this.name; + } + + public int ping() { + return this.ping; + } + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingSummary.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingSummary.java new file mode 100644 index 0000000..024d27d --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingSummary.java @@ -0,0 +1,81 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.monitor.ping; + +import java.util.Arrays; + +public final class PingSummary { + + private final int[] values; + private final int total; + private final int max; + private final int min; + private final double mean; + + public PingSummary(int[] values) { + Arrays.sort(values); + this.values = values; + + int total = 0; + for (int value : values) { + total += value; + } + this.total = total; + + this.mean = (double) total / values.length; + this.max = values[values.length - 1]; + this.min = values[0]; + } + + public int total() { + return this.total; + } + + public double mean() { + return this.mean; + } + + public int max() { + return this.max; + } + + public int min() { + return this.min; + } + + public int percentile(double percentile) { + if (percentile < 0 || percentile > 1) { + throw new IllegalArgumentException("Invalid percentile " + percentile); + } + + int rank = (int) Math.ceil(percentile * (this.values.length - 1)); + return this.values[rank]; + } + + public double median() { + return percentile(0.50d); + } + + public double percentile95th() { + return percentile(0.95d); + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PlayerPingProvider.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PlayerPingProvider.java new file mode 100644 index 0000000..7576573 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PlayerPingProvider.java @@ -0,0 +1,40 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.monitor.ping; + +import java.util.Map; + +/** + * Provides information about player ping RTT. + */ +@FunctionalInterface +public interface PlayerPingProvider { + + /** + * Poll current player pings in milliseconds. + * + *

The map keys are player names and the values are the ping values.

+ * + * @return a map of player pings + */ + Map poll(); + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java index fce45ec..5b8d1d4 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java @@ -26,6 +26,7 @@ import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.disk.DiskUsage; import me.lucko.spark.common.monitor.memory.GarbageCollectorStatistics; import me.lucko.spark.common.monitor.memory.MemoryInfo; +import me.lucko.spark.common.monitor.ping.PingStatistics; import me.lucko.spark.common.monitor.tick.TickStatistics; import me.lucko.spark.common.util.RollingAverage; import me.lucko.spark.proto.SparkProtos.PlatformStatistics; @@ -148,18 +149,26 @@ public class PlatformStatisticsProvider { ); if (tickStatistics.isDurationSupported()) { builder.setMspt(PlatformStatistics.Mspt.newBuilder() - .setLast1M(msptValues(tickStatistics.duration1Min())) - .setLast5M(msptValues(tickStatistics.duration5Min())) + .setLast1M(rollingAverageValues(tickStatistics.duration1Min())) + .setLast5M(rollingAverageValues(tickStatistics.duration5Min())) .build() ); } } + PingStatistics pingStatistics = this.platform.getPingStatistics(); + if (pingStatistics != null && pingStatistics.getPingAverage().getSamples() != 0) { + builder.setPing(PlatformStatistics.Ping.newBuilder() + .setLast15M(rollingAverageValues(pingStatistics.getPingAverage())) + .build() + ); + } + return builder.build(); } - private static PlatformStatistics.Mspt.Values msptValues(RollingAverage rollingAverage) { - return PlatformStatistics.Mspt.Values.newBuilder() + private static PlatformStatistics.RollingAverageValues rollingAverageValues(RollingAverage rollingAverage) { + return PlatformStatistics.RollingAverageValues.newBuilder() .setMean(rollingAverage.mean()) .setMax(rollingAverage.max()) .setMin(rollingAverage.min()) diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java b/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java index 87c41a4..57dfdff 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java @@ -39,6 +39,12 @@ public class RollingAverage implements DoubleAverageInfo { this.samples = new ArrayDeque<>(this.windowSize + 1); } + public int getSamples() { + synchronized (this) { + return this.samples.size(); + } + } + public void add(BigDecimal num) { synchronized (this) { this.total = this.total.add(num); diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java b/spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java new file mode 100644 index 0000000..9a2850e --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java @@ -0,0 +1,187 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.util; + +import com.google.common.base.Strings; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.TextColor; + +import java.lang.management.MemoryUsage; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY; +import static net.kyori.adventure.text.format.NamedTextColor.GRAY; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +import static net.kyori.adventure.text.format.NamedTextColor.RED; +import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; + +public enum StatisticFormatter { + ; + + private static final String BAR_CHARACTER = "|"; + + public static TextComponent formatTps(double tps) { + TextColor color; + if (tps > 18.0) { + color = GREEN; + } else if (tps > 16.0) { + color = YELLOW; + } else { + color = RED; + } + + return text((tps > 20.0 ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0), color); + } + + public static TextComponent formatTickDurations(RollingAverage average) { + return text() + .append(formatTickDuration(average.min())) + .append(text('/', GRAY)) + .append(formatTickDuration(average.median())) + .append(text('/', GRAY)) + .append(formatTickDuration(average.percentile95th())) + .append(text('/', GRAY)) + .append(formatTickDuration(average.max())) + .build(); + } + + public static TextComponent formatTickDuration(double duration) { + TextColor color; + if (duration >= 50d) { + color = RED; + } else if (duration >= 40d) { + color = YELLOW; + } else { + color = GREEN; + } + + return text(String.format("%.1f", duration), color); + } + + public static TextComponent formatCpuUsage(double usage) { + TextColor color; + if (usage > 0.9) { + color = RED; + } else if (usage > 0.65) { + color = YELLOW; + } else { + color = GREEN; + } + + return text(FormatUtil.percent(usage, 1d), color); + } + + public static TextComponent formatPingRtts(double min, double median, double percentile95th, double max) { + return text() + .append(formatPingRtt(min)) + .append(text('/', GRAY)) + .append(formatPingRtt(median)) + .append(text('/', GRAY)) + .append(formatPingRtt(percentile95th)) + .append(text('/', GRAY)) + .append(formatPingRtt(max)) + .build(); + } + + public static TextComponent formatPingRtt(double ping) { + TextColor color; + if (ping >= 200) { + color = RED; + } else if (ping >= 100) { + color = YELLOW; + } else { + color = GREEN; + } + + return text((int) Math.ceil(ping), color); + } + + public static TextComponent generateMemoryUsageDiagram(MemoryUsage usage, int length) { + double used = usage.getUsed(); + double committed = usage.getCommitted(); + double max = usage.getMax(); + + int usedChars = (int) ((used * length) / max); + int committedChars = (int) ((committed * length) / max); + + TextComponent.Builder line = text().content(Strings.repeat(BAR_CHARACTER, usedChars)).color(YELLOW); + if (committedChars > usedChars) { + line.append(text(Strings.repeat(BAR_CHARACTER, (committedChars - usedChars) - 1), GRAY)); + line.append(Component.text(BAR_CHARACTER, RED)); + } + if (length > committedChars) { + line.append(text(Strings.repeat(BAR_CHARACTER, (length - committedChars)), GRAY)); + } + + return text() + .append(text("[", DARK_GRAY)) + .append(line.build()) + .append(text("]", DARK_GRAY)) + .build(); + } + + public static TextComponent generateMemoryPoolDiagram(MemoryUsage usage, MemoryUsage collectionUsage, int length) { + double used = usage.getUsed(); + double collectionUsed = used; + if (collectionUsage != null) { + collectionUsed = collectionUsage.getUsed(); + } + double committed = usage.getCommitted(); + double max = usage.getMax(); + + int usedChars = (int) ((used * length) / max); + int collectionUsedChars = (int) ((collectionUsed * length) / max); + int committedChars = (int) ((committed * length) / max); + + TextComponent.Builder line = text().content(Strings.repeat(BAR_CHARACTER, collectionUsedChars)).color(YELLOW); + + if (usedChars > collectionUsedChars) { + line.append(Component.text(BAR_CHARACTER, RED)); + line.append(text(Strings.repeat(BAR_CHARACTER, (usedChars - collectionUsedChars) - 1), YELLOW)); + } + if (committedChars > usedChars) { + line.append(text(Strings.repeat(BAR_CHARACTER, (committedChars - usedChars) - 1), GRAY)); + line.append(Component.text(BAR_CHARACTER, YELLOW)); + } + if (length > committedChars) { + line.append(text(Strings.repeat(BAR_CHARACTER, (length - committedChars)), GRAY)); + } + + return text() + .append(text("[", DARK_GRAY)) + .append(line.build()) + .append(text("]", DARK_GRAY)) + .build(); + } + + public static TextComponent generateDiskUsageDiagram(double used, double max, int length) { + int usedChars = (int) ((used * length) / max); + int freeChars = length - usedChars; + return text() + .append(text("[", DARK_GRAY)) + .append(text(Strings.repeat(BAR_CHARACTER, usedChars), YELLOW)) + .append(text(Strings.repeat(BAR_CHARACTER, freeChars), GRAY)) + .append(text("]", DARK_GRAY)) + .build(); + } +} diff --git a/spark-common/src/main/proto/spark/spark.proto b/spark-common/src/main/proto/spark/spark.proto index ae04cd7..d800762 100644 --- a/spark-common/src/main/proto/spark/spark.proto +++ b/spark-common/src/main/proto/spark/spark.proto @@ -85,6 +85,7 @@ message PlatformStatistics { int64 uptime = 3; Tps tps = 4; // optional Mspt mspt = 5; // optional + Ping ping = 6; // optional message Memory { MemoryPool heap = 1; @@ -108,16 +109,20 @@ message PlatformStatistics { } message Mspt { - Values last1m = 1; - Values last5m = 2; - - message Values { - double mean = 1; - double max = 2; - double min = 3; - double median = 4; - double percentile95 = 5; - } + RollingAverageValues last1m = 1; + RollingAverageValues last5m = 2; + } + + message Ping { + RollingAverageValues last15m = 1; + } + + message RollingAverageValues { + double mean = 1; + double max = 2; + double min = 3; + double median = 4; + double percentile95 = 5; } } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricPlayerPingProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricPlayerPingProvider.java new file mode 100644 index 0000000..bae6c41 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricPlayerPingProvider.java @@ -0,0 +1,47 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.fabric; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.Map; + +public class FabricPlayerPingProvider implements PlayerPingProvider { + private final MinecraftServer server; + + public FabricPlayerPingProvider(MinecraftServer server) { + this.server = server; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (ServerPlayerEntity player : this.server.getPlayerManager().getPlayerList()) { + builder.put(player.getGameProfile().getName(), player.pingMilliseconds); + } + return builder.build(); + } +} diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java index 9171cbb..b9cff691 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java @@ -4,10 +4,10 @@ import eu.pb4.placeholders.PlaceholderAPI; import eu.pb4.placeholders.PlaceholderResult; import me.lucko.spark.common.SparkPlatform; -import me.lucko.spark.common.command.modules.HealthModule; import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.tick.TickStatistics; import me.lucko.spark.common.util.RollingAverage; +import me.lucko.spark.common.util.StatisticFormatter; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -47,16 +47,16 @@ public class SparkFabricPlaceholderApi { if (tps == null) { return PlaceholderResult.invalid("Invalid argument"); } else { - return PlaceholderResult.value(toText(HealthModule.formatTps(tps))); + return PlaceholderResult.value(toText(StatisticFormatter.formatTps(tps))); } } else { return PlaceholderResult.value(toText( Component.text() - .append(HealthModule.formatTps(tickStatistics.tps5Sec())).append(Component.text(", ")) - .append(HealthModule.formatTps(tickStatistics.tps10Sec())).append(Component.text(", ")) - .append(HealthModule.formatTps(tickStatistics.tps1Min())).append(Component.text(", ")) - .append(HealthModule.formatTps(tickStatistics.tps5Min())).append(Component.text(", ")) - .append(HealthModule.formatTps(tickStatistics.tps15Min())) + .append(StatisticFormatter.formatTps(tickStatistics.tps5Sec())).append(Component.text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps10Sec())).append(Component.text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps1Min())).append(Component.text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps5Min())).append(Component.text(", ")) + .append(StatisticFormatter.formatTps(tickStatistics.tps15Min())) .build() )); } @@ -84,13 +84,13 @@ public class SparkFabricPlaceholderApi { if (duration == null) { return PlaceholderResult.invalid("Invalid argument"); } else { - return PlaceholderResult.value(toText(HealthModule.formatTickDurations(duration))); + return PlaceholderResult.value(toText(StatisticFormatter.formatTickDurations(duration))); } } else { return PlaceholderResult.value(toText( Component.text() - .append(HealthModule.formatTickDurations(tickStatistics.duration10Sec())).append(Component.text("; ")) - .append(HealthModule.formatTickDurations(tickStatistics.duration1Min())) + .append(StatisticFormatter.formatTickDurations(tickStatistics.duration10Sec())).append(Component.text("; ")) + .append(StatisticFormatter.formatTickDurations(tickStatistics.duration1Min())) .build() )); } @@ -115,14 +115,14 @@ public class SparkFabricPlaceholderApi { if (usage == null) { return PlaceholderResult.invalid("Invalid argument"); } else { - return PlaceholderResult.value(toText(HealthModule.formatCpuUsage(usage))); + return PlaceholderResult.value(toText(StatisticFormatter.formatCpuUsage(usage))); } } else { return PlaceholderResult.value(toText( Component.text() - .append(HealthModule.formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(Component.text(", ")) - .append(HealthModule.formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(Component.text(", ")) - .append(HealthModule.formatCpuUsage(CpuMonitor.systemLoad15MinAvg())) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(Component.text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(Component.text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad15MinAvg())) .build() )); } @@ -147,14 +147,14 @@ public class SparkFabricPlaceholderApi { if (usage == null) { return PlaceholderResult.invalid("Invalid argument"); } else { - return PlaceholderResult.value(toText(HealthModule.formatCpuUsage(usage))); + return PlaceholderResult.value(toText(StatisticFormatter.formatCpuUsage(usage))); } } else { return PlaceholderResult.value(toText( Component.text() - .append(HealthModule.formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(Component.text(", ")) - .append(HealthModule.formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(Component.text(", ")) - .append(HealthModule.formatCpuUsage(CpuMonitor.processLoad15MinAvg())) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(Component.text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(Component.text(", ")) + .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad15MinAvg())) .build() )); } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java index 617564a..6dc5483 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java @@ -29,11 +29,13 @@ import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import me.lucko.fabric.api.permissions.v0.Permissions; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; import me.lucko.spark.fabric.FabricCommandSender; import me.lucko.spark.fabric.FabricPlatformInfo; +import me.lucko.spark.fabric.FabricPlayerPingProvider; import me.lucko.spark.fabric.FabricSparkMod; import me.lucko.spark.fabric.FabricTickHook; import me.lucko.spark.fabric.FabricTickReporter; @@ -140,6 +142,11 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman return new FabricTickReporter.Server(); } + @Override + public PlayerPingProvider createPlayerPingProvider() { + return new FabricPlayerPingProvider(this.server); + } + @Override public PlatformInfo getPlatformInfo() { return new FabricPlatformInfo(PlatformInfo.Type.SERVER); diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/ForgePlayerPingProvider.java b/spark-forge/src/main/java/me/lucko/spark/forge/ForgePlayerPingProvider.java new file mode 100644 index 0000000..f31cc5b --- /dev/null +++ b/spark-forge/src/main/java/me/lucko/spark/forge/ForgePlayerPingProvider.java @@ -0,0 +1,47 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.forge; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; + +import java.util.Map; + +public class ForgePlayerPingProvider implements PlayerPingProvider { + private final MinecraftServer server; + + public ForgePlayerPingProvider(MinecraftServer server) { + this.server = server; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (ServerPlayer player : this.server.getPlayerList().getPlayers()) { + builder.put(player.getGameProfile().getName(), player.latency); + } + return builder.build(); + } +} diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java index c03bb05..bbfb8a5 100644 --- a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java +++ b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java @@ -29,11 +29,13 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; import me.lucko.spark.forge.ForgeCommandSender; import me.lucko.spark.forge.ForgePlatformInfo; +import me.lucko.spark.forge.ForgePlayerPingProvider; import me.lucko.spark.forge.ForgeSparkMod; import me.lucko.spark.forge.ForgeTickHook; import me.lucko.spark.forge.ForgeTickReporter; @@ -200,6 +202,11 @@ public class ForgeServerSparkPlugin extends ForgeSparkPlugin implements Command< return new ForgeTickReporter(TickEvent.Type.SERVER); } + @Override + public PlayerPingProvider createPlayerPingProvider() { + return new ForgePlayerPingProvider(this.server); + } + @Override public PlatformInfo getPlatformInfo() { return new ForgePlatformInfo(PlatformInfo.Type.SERVER); diff --git a/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitPlayerPingProvider.java b/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitPlayerPingProvider.java new file mode 100644 index 0000000..fc25d7c --- /dev/null +++ b/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitPlayerPingProvider.java @@ -0,0 +1,47 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.nukkit; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; + +import cn.nukkit.Player; +import cn.nukkit.Server; + +import java.util.Map; + +public class NukkitPlayerPingProvider implements PlayerPingProvider { + private final Server server; + + public NukkitPlayerPingProvider(Server server) { + this.server = server; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Player player : this.server.getOnlinePlayers().values()) { + builder.put(player.getName(), player.getPing()); + } + return builder.build(); + } +} diff --git a/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitSparkPlugin.java b/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitSparkPlugin.java index c9e099d..18132c3 100644 --- a/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitSparkPlugin.java +++ b/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitSparkPlugin.java @@ -23,6 +23,7 @@ package me.lucko.spark.nukkit; import me.lucko.spark.api.Spark; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.util.ClassSourceLookup; @@ -107,6 +108,11 @@ public class NukkitSparkPlugin extends PluginBase implements SparkPlugin { return new NukkitClassSourceLookup(); } + @Override + public PlayerPingProvider createPlayerPingProvider() { + return new NukkitPlayerPingProvider(getServer()); + } + @Override public PlatformInfo getPlatformInfo() { return new NukkitPlatformInfo(getServer()); diff --git a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7PlayerPingProvider.java b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7PlayerPingProvider.java new file mode 100644 index 0000000..8f4c15f --- /dev/null +++ b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7PlayerPingProvider.java @@ -0,0 +1,47 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.sponge; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; + +import org.spongepowered.api.Server; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.Map; + +public class Sponge7PlayerPingProvider implements PlayerPingProvider { + private final Server server; + + public Sponge7PlayerPingProvider(Server server) { + this.server = server; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Player player : this.server.getOnlinePlayers()) { + builder.put(player.getName(), player.getConnection().getLatency()); + } + return builder.build(); + } +} diff --git a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java index 7ac317b..1aed04a 100644 --- a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java +++ b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java @@ -25,6 +25,7 @@ import com.google.inject.Inject; import me.lucko.spark.api.Spark; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; @@ -144,6 +145,15 @@ public class Sponge7SparkPlugin implements SparkPlugin { return new Sponge7TickHook(this); } + @Override + public PlayerPingProvider createPlayerPingProvider() { + if (this.game.isServerAvailable()) { + return new Sponge7PlayerPingProvider(this.game.getServer()); + } else { + return null; + } + } + @Override public PlatformInfo getPlatformInfo() { return new Sponge7PlatformInfo(this.game); diff --git a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8PlayerPingProvider.java b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8PlayerPingProvider.java new file mode 100644 index 0000000..2bcaf6a --- /dev/null +++ b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8PlayerPingProvider.java @@ -0,0 +1,47 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.sponge; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; + +import org.spongepowered.api.Server; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; + +import java.util.Map; + +public class Sponge8PlayerPingProvider implements PlayerPingProvider { + private final Server server; + + public Sponge8PlayerPingProvider(Server server) { + this.server = server; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (ServerPlayer player : this.server.onlinePlayers()) { + builder.put(player.name(), player.connection().latency()); + } + return builder.build(); + } +} diff --git a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8SparkPlugin.java b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8SparkPlugin.java index 0195b24..6eb2674 100644 --- a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8SparkPlugin.java +++ b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8SparkPlugin.java @@ -25,6 +25,7 @@ import com.google.inject.Inject; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.command.sender.CommandSender; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; @@ -146,6 +147,15 @@ public class Sponge8SparkPlugin implements SparkPlugin { return new Sponge8TickHook(this.pluginContainer, this.game); } + @Override + public PlayerPingProvider createPlayerPingProvider() { + if (this.game.isServerAvailable()) { + return new Sponge8PlayerPingProvider(this.game.server()); + } else { + return null; + } + } + @Override public PlatformInfo getPlatformInfo() { return new Sponge8PlatformInfo(this.game); diff --git a/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocityPlayerPingProvider.java b/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocityPlayerPingProvider.java new file mode 100644 index 0000000..382ea22 --- /dev/null +++ b/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocityPlayerPingProvider.java @@ -0,0 +1,46 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.velocity; + +import com.google.common.collect.ImmutableMap; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; + +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; + +import java.util.Map; + +public class VelocityPlayerPingProvider implements PlayerPingProvider { + private final ProxyServer proxy; + + public VelocityPlayerPingProvider(ProxyServer proxy) { + this.proxy = proxy; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Player player : this.proxy.getAllPlayers()) { + builder.put(player.getUsername(), (int) player.getPing()); + } + return builder.build(); + } +} diff --git a/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocitySparkPlugin.java b/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocitySparkPlugin.java index 698aab0..7d9ced8 100644 --- a/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocitySparkPlugin.java +++ b/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocitySparkPlugin.java @@ -32,6 +32,7 @@ import com.velocitypowered.api.proxy.ProxyServer; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.util.ClassSourceLookup; @@ -132,6 +133,11 @@ public class VelocitySparkPlugin implements SparkPlugin, SimpleCommand { return new VelocityClassSourceLookup(this.proxy.getPluginManager()); } + @Override + public PlayerPingProvider createPlayerPingProvider() { + return new VelocityPlayerPingProvider(this.proxy); + } + @Override public PlatformInfo getPlatformInfo() { return new VelocityPlatformInfo(this.proxy); diff --git a/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4PlayerPingProvider.java b/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4PlayerPingProvider.java new file mode 100644 index 0000000..18f36a7 --- /dev/null +++ b/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4PlayerPingProvider.java @@ -0,0 +1,46 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.velocity; + +import com.google.common.collect.ImmutableMap; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.connection.Player; + +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; + +import java.util.Map; + +public class Velocity4PlayerPingProvider implements PlayerPingProvider { + private final ProxyServer proxy; + + public Velocity4PlayerPingProvider(ProxyServer proxy) { + this.proxy = proxy; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Player player : this.proxy.connectedPlayers()) { + builder.put(player.username(), (int) player.ping()); + } + return builder.build(); + } +} diff --git a/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4SparkPlugin.java b/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4SparkPlugin.java index e1acd57..0c57689 100644 --- a/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4SparkPlugin.java +++ b/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4SparkPlugin.java @@ -32,6 +32,7 @@ import com.velocitypowered.api.proxy.ProxyServer; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.util.ClassSourceLookup; @@ -132,6 +133,11 @@ public class Velocity4SparkPlugin implements SparkPlugin, SimpleCommand { return new Velocity4ClassSourceLookup(this.proxy.pluginManager()); } + @Override + public PlayerPingProvider createPlayerPingProvider() { + return new Velocity4PlayerPingProvider(this.proxy); + } + @Override public PlatformInfo getPlatformInfo() { return new Velocity4PlatformInfo(this.proxy); diff --git a/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogPlayerPingProvider.java b/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogPlayerPingProvider.java new file mode 100644 index 0000000..b22325c --- /dev/null +++ b/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogPlayerPingProvider.java @@ -0,0 +1,47 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.waterdog; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; + +import dev.waterdog.waterdogpe.ProxyServer; +import dev.waterdog.waterdogpe.player.ProxiedPlayer; + +import java.util.Map; + +public class WaterdogPlayerPingProvider implements PlayerPingProvider { + private final ProxyServer proxy; + + public WaterdogPlayerPingProvider(ProxyServer proxy) { + this.proxy = proxy; + } + + @Override + public Map poll() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (ProxiedPlayer player : this.proxy.getPlayers().values()) { + builder.put(player.getName(), (int) player.getPing()); + } + return builder.build(); + } +} diff --git a/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogSparkPlugin.java b/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogSparkPlugin.java index fd2f031..07b153a 100644 --- a/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogSparkPlugin.java +++ b/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogSparkPlugin.java @@ -22,6 +22,7 @@ package me.lucko.spark.waterdog; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.util.ClassSourceLookup; @@ -99,6 +100,11 @@ public class WaterdogSparkPlugin extends Plugin implements SparkPlugin { return new WaterdogClassSourceLookup(getProxy()); } + @Override + public PlayerPingProvider createPlayerPingProvider() { + return new WaterdogPlayerPingProvider(getProxy()); + } + @Override public PlatformInfo getPlatformInfo() { return new WaterdogPlatformInfo(); -- cgit From fe9b39b500ee2633db18ea14774daf01e2946824 Mon Sep 17 00:00:00 2001 From: Manuel Kasten Date: Fri, 21 Jan 2022 21:09:46 +0100 Subject: Add disableResponseBroadcast config option (#158) --- .../src/main/java/me/lucko/spark/common/SparkPlatform.java | 7 +++++++ .../lucko/spark/common/command/CommandResponseHandler.java | 14 +++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) (limited to 'spark-common/src/main/java/me/lucko/spark/common/command') diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java index c93b876..a087fc9 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java @@ -94,6 +94,7 @@ public class SparkPlatform { private final String viewerUrl; private final OkHttpClient httpClient; private final BytebinClient bytebinClient; + private final boolean disableResponseBroadcast; private final List commandModules; private final List commands; private final ReentrantLock commandExecuteLock = new ReentrantLock(true); @@ -118,6 +119,8 @@ public class SparkPlatform { this.httpClient = new OkHttpClient(); this.bytebinClient = new BytebinClient(this.httpClient, bytebinUrl, "spark-plugin"); + this.disableResponseBroadcast = this.configuration.getBoolean("disableResponseBroadcast", false); + this.commandModules = ImmutableList.of( new SamplerModule(), new HealthModule(), @@ -216,6 +219,10 @@ public class SparkPlatform { return this.bytebinClient; } + public boolean shouldBroadcastResponse() { + return !this.disableResponseBroadcast; + } + public List getCommands() { return this.commands; } diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/CommandResponseHandler.java b/spark-common/src/main/java/me/lucko/spark/common/command/CommandResponseHandler.java index a9e2229..d1481bd 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/CommandResponseHandler.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/CommandResponseHandler.java @@ -88,12 +88,20 @@ public class CommandResponseHandler { } public void broadcast(Component message) { - allSenders(sender -> sender.sendMessage(message)); + if (this.platform.shouldBroadcastResponse()) { + allSenders(sender -> sender.sendMessage(message)); + } else { + reply(message); + } } public void broadcast(Iterable message) { - Component joinedMsg = Component.join(JoinConfiguration.separator(Component.newline()), message); - allSenders(sender -> sender.sendMessage(joinedMsg)); + if (this.platform.shouldBroadcastResponse()) { + Component joinedMsg = Component.join(JoinConfiguration.separator(Component.newline()), message); + allSenders(sender -> sender.sendMessage(joinedMsg)); + } else { + reply(message); + } } public void replyPrefixed(Component message) { -- cgit From 25f46b649363d99f51b1b5262b5f7c0ce0c3251b Mon Sep 17 00:00:00 2001 From: Luck Date: Sun, 6 Feb 2022 20:25:07 +0000 Subject: Improve build tooling - add version to output file name - check license headers automatically --- HEADER.txt | 17 +++++++++++ build.gradle | 27 ++++++++++++++++-- settings.gradle | 4 +-- spark-api/HEADER.txt | 22 +++++++++++++++ spark-api/build.gradle | 4 +++ spark-bukkit/build.gradle | 29 ++++++++++++++++++- .../java/me/lucko/spark/bukkit/CommandMapUtil.java | 27 ++++++++---------- .../placeholder/SparkPlaceholderProvider.java | 2 +- spark-bungeecord/build.gradle | 29 ++++++++++++++++++- spark-common/build.gradle | 4 +++ .../command/tabcomplete/CompletionSupplier.java | 27 ++++++++---------- .../common/command/tabcomplete/TabCompleter.java | 27 ++++++++---------- .../spark/common/monitor/MonitoringExecutor.java | 20 +++++++++++++ .../lucko/spark/common/sampler/ThreadDumper.java | 1 - .../spark/common/sampler/java/JavaSampler.java | 1 - .../spark/common/sampler/node/AbstractNode.java | 1 - .../spark/common/sampler/node/StackTraceNode.java | 1 - .../me/lucko/spark/common/util/BytebinClient.java | 27 ++++++++---------- spark-fabric/build.gradle | 4 +-- .../placeholder/SparkFabricPlaceholderApi.java | 20 +++++++++++++ spark-forge/build.gradle | 2 +- spark-nukkit/build.gradle | 2 +- spark-sponge7/build.gradle | 2 +- spark-sponge8/build.gradle | 2 +- .../lucko/spark/sponge/Sponge8CommandSender.java | 1 + spark-universal/build.gradle | 33 ---------------------- spark-velocity/build.gradle | 2 +- spark-velocity4/build.gradle | 2 +- spark-waterdog/build.gradle | 2 +- 29 files changed, 224 insertions(+), 118 deletions(-) create mode 100644 HEADER.txt create mode 100644 spark-api/HEADER.txt delete mode 100644 spark-universal/build.gradle (limited to 'spark-common/src/main/java/me/lucko/spark/common/command') diff --git a/HEADER.txt b/HEADER.txt new file mode 100644 index 0000000..3457e84 --- /dev/null +++ b/HEADER.txt @@ -0,0 +1,17 @@ +This file is part of spark. + + Copyright (c) lucko (Luck) + Copyright (c) contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9e64e75..2780159 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,10 @@ +plugins { + id 'org.cadixdev.licenser' version '0.6.1' apply false +} + allprojects { group = 'me.lucko' - version = '1.6-SNAPSHOT' + version = '1.8-SNAPSHOT' configurations { compileClasspath // Fabric-loom needs this for remap jar for some reason @@ -11,9 +15,12 @@ subprojects { apply plugin: 'java' apply plugin: 'idea' apply plugin: 'java-library' + apply plugin: 'org.cadixdev.licenser' ext { - pluginVersion = '1.8.0' + baseVersion = '1.8' + patchVersion = determinePatchVersion() + pluginVersion = baseVersion + '.' + patchVersion pluginDescription = 'spark is a performance profiling plugin/mod for Minecraft clients, servers and proxies.' } @@ -26,6 +33,11 @@ subprojects { duplicatesStrategy = DuplicatesStrategy.INCLUDE } + license { + header = rootProject.file('HEADER.txt') + include '**/*.java' + } + repositories { mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } @@ -34,3 +46,14 @@ subprojects { } } + +def determinePatchVersion() { + def tagInfo = new ByteArrayOutputStream() + exec { + commandLine 'git', 'describe', '--tags' + standardOutput = tagInfo + } + tagInfo = tagInfo.toString() + + return tagInfo.contains('-') ? tagInfo.split("-")[1] : 0 +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 09d0a60..8ec8a72 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,5 @@ pluginManagement { repositories { - jcenter() maven { name = 'Fabric' url = 'https://maven.fabricmc.net/' @@ -22,6 +21,5 @@ include ( 'spark-forge', 'spark-fabric', 'spark-nukkit', - 'spark-waterdog', - 'spark-universal' + 'spark-waterdog' ) diff --git a/spark-api/HEADER.txt b/spark-api/HEADER.txt new file mode 100644 index 0000000..63aafcb --- /dev/null +++ b/spark-api/HEADER.txt @@ -0,0 +1,22 @@ +This file is part of spark, licensed under the MIT License. + + Copyright (c) lucko (Luck) + Copyright (c) contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. \ No newline at end of file diff --git a/spark-api/build.gradle b/spark-api/build.gradle index 31458af..0fbe9e1 100644 --- a/spark-api/build.gradle +++ b/spark-api/build.gradle @@ -9,6 +9,10 @@ dependencies { compileOnly 'org.jetbrains:annotations:20.1.0' } +license { + header = project.file('HEADER.txt') +} + publishing { //repositories { // maven { diff --git a/spark-bukkit/build.gradle b/spark-bukkit/build.gradle index b66134d..8e111e8 100644 --- a/spark-bukkit/build.gradle +++ b/spark-bukkit/build.gradle @@ -1,3 +1,7 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '7.0.0' +} + dependencies { implementation project(':spark-common') implementation('me.lucko:adventure-platform-bukkit:4.9.4') { @@ -28,4 +32,27 @@ processResources { ) include 'plugin.yml' } -} \ No newline at end of file +} + +shadowJar { + archiveName = "spark-${project.pluginVersion}-bukkit.jar" + + relocate 'okio', 'me.lucko.spark.lib.okio' + relocate 'okhttp3', 'me.lucko.spark.lib.okhttp3' + relocate 'net.kyori.adventure', 'me.lucko.spark.lib.adventure' + relocate 'net.kyori.examination', 'me.lucko.spark.lib.adventure.examination' + relocate 'net.bytebuddy', 'me.lucko.spark.lib.bytebuddy' + relocate 'org.tukaani.xz', 'me.lucko.spark.lib.xz' + relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' + relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' + relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + + exclude 'module-info.class' + exclude 'META-INF/maven/**' + exclude 'META-INF/proguard/**' +} + +artifacts { + archives shadowJar + shadow shadowJar +} diff --git a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/CommandMapUtil.java b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/CommandMapUtil.java index 5c4d0db..e604321 100644 --- a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/CommandMapUtil.java +++ b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/CommandMapUtil.java @@ -1,26 +1,21 @@ /* - * This file is part of helper, licensed under the MIT License. + * This file is part of spark. * * Copyright (c) lucko (Luck) * Copyright (c) contributors * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package me.lucko.spark.bukkit; diff --git a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/placeholder/SparkPlaceholderProvider.java b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/placeholder/SparkPlaceholderProvider.java index 96c9e93..5b57857 100644 --- a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/placeholder/SparkPlaceholderProvider.java +++ b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/placeholder/SparkPlaceholderProvider.java @@ -3,7 +3,7 @@ * * Copyright (c) lucko (Luck) * Copyright (c) contributors - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or diff --git a/spark-bungeecord/build.gradle b/spark-bungeecord/build.gradle index f1a6db5..ccea89d 100644 --- a/spark-bungeecord/build.gradle +++ b/spark-bungeecord/build.gradle @@ -1,3 +1,7 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '7.0.0' +} + dependencies { implementation project(':spark-common') implementation('me.lucko:adventure-platform-bungeecord:4.9.4') { @@ -18,4 +22,27 @@ processResources { ) include 'bungee.yml' } -} \ No newline at end of file +} + +shadowJar { + archiveName = "spark-${project.pluginVersion}-bungeecord.jar" + + relocate 'okio', 'me.lucko.spark.lib.okio' + relocate 'okhttp3', 'me.lucko.spark.lib.okhttp3' + relocate 'net.kyori.adventure', 'me.lucko.spark.lib.adventure' + relocate 'net.kyori.examination', 'me.lucko.spark.lib.adventure.examination' + relocate 'net.bytebuddy', 'me.lucko.spark.lib.bytebuddy' + relocate 'org.tukaani.xz', 'me.lucko.spark.lib.xz' + relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' + relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' + relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + + exclude 'module-info.class' + exclude 'META-INF/maven/**' + exclude 'META-INF/proguard/**' +} + +artifacts { + archives shadowJar + shadow shadowJar +} diff --git a/spark-common/build.gradle b/spark-common/build.gradle index c1ddafb..aa0f409 100644 --- a/spark-common/build.gradle +++ b/spark-common/build.gradle @@ -2,6 +2,10 @@ plugins { id 'com.google.protobuf' version '0.8.16' } +license { + exclude '**/sampler/async/jfr/**' +} + dependencies { api project(':spark-api') implementation 'com.github.jvm-profiling-tools:async-profiler:v2.5' diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/CompletionSupplier.java b/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/CompletionSupplier.java index f1a6d10..9975df5 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/CompletionSupplier.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/CompletionSupplier.java @@ -1,26 +1,21 @@ /* - * This file is part of LuckPerms, licensed under the MIT License. + * This file is part of spark. * * Copyright (c) lucko (Luck) * Copyright (c) contributors * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package me.lucko.spark.common.command.tabcomplete; diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/TabCompleter.java b/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/TabCompleter.java index d2b2622..9707f55 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/TabCompleter.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/tabcomplete/TabCompleter.java @@ -1,26 +1,21 @@ /* - * This file is part of LuckPerms, licensed under the MIT License. + * This file is part of spark. * * Copyright (c) lucko (Luck) * Copyright (c) contributors * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package me.lucko.spark.common.command.tabcomplete; diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/MonitoringExecutor.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/MonitoringExecutor.java index 779dbbf..635ae20 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/monitor/MonitoringExecutor.java +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/MonitoringExecutor.java @@ -1,3 +1,23 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package me.lucko.spark.common.monitor; import java.util.concurrent.Executors; diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/ThreadDumper.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/ThreadDumper.java index 5cc41b9..9d54f50 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/ThreadDumper.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/ThreadDumper.java @@ -1,7 +1,6 @@ /* * This file is part of spark. * - * Copyright (C) Albert Pham * Copyright (c) lucko (Luck) * Copyright (c) contributors * diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java index d2959bd..2bedae6 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java @@ -1,7 +1,6 @@ /* * This file is part of spark. * - * Copyright (C) Albert Pham * Copyright (c) lucko (Luck) * Copyright (c) contributors * diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/node/AbstractNode.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/node/AbstractNode.java index 18f67ba..fd2be8d 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/node/AbstractNode.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/node/AbstractNode.java @@ -1,7 +1,6 @@ /* * This file is part of spark. * - * Copyright (C) Albert Pham * Copyright (c) lucko (Luck) * Copyright (c) contributors * diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/node/StackTraceNode.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/node/StackTraceNode.java index 54217be..b0d9237 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/node/StackTraceNode.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/node/StackTraceNode.java @@ -1,7 +1,6 @@ /* * This file is part of spark. * - * Copyright (C) Albert Pham * Copyright (c) lucko (Luck) * Copyright (c) contributors * diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/BytebinClient.java b/spark-common/src/main/java/me/lucko/spark/common/util/BytebinClient.java index 29ee5bb..c2ca1b1 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/BytebinClient.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/BytebinClient.java @@ -1,26 +1,21 @@ /* - * This file is part of bytebin, licensed under the MIT License. + * This file is part of spark. * * Copyright (c) lucko (Luck) * Copyright (c) contributors * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package me.lucko.spark.common.util; diff --git a/spark-fabric/build.gradle b/spark-fabric/build.gradle index d7e87cd..8cdf5fe 100644 --- a/spark-fabric/build.gradle +++ b/spark-fabric/build.gradle @@ -67,7 +67,7 @@ processResources { } shadowJar { - archiveFileName = 'spark-fabric-dev.jar' + archiveFileName = "spark-fabric-${project.pluginVersion}-dev.jar" configurations = [project.configurations.shade] relocate 'okio', 'me.lucko.spark.lib.okio' @@ -89,7 +89,7 @@ task remappedShadowJar(type: RemapJarTask) { dependsOn tasks.shadowJar input = tasks.shadowJar.archiveFile addNestedDependencies = true - archiveFileName = 'spark-fabric.jar' + archiveFileName = "spark-${project.pluginVersion}-fabric.jar" } tasks.assemble.dependsOn tasks.remappedShadowJar diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java index b9cff691..dc2e7d9 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java @@ -1,3 +1,23 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package me.lucko.spark.fabric.placeholder; import eu.pb4.placeholders.PlaceholderAPI; diff --git a/spark-forge/build.gradle b/spark-forge/build.gradle index e0fe0f8..25e51ef 100644 --- a/spark-forge/build.gradle +++ b/spark-forge/build.gradle @@ -49,7 +49,7 @@ processResources { } shadowJar { - archiveName = 'spark-forge.jar' + archiveName = "spark-${project.pluginVersion}-forge.jar" configurations = [project.configurations.shade] relocate 'okio', 'me.lucko.spark.lib.okio' diff --git a/spark-nukkit/build.gradle b/spark-nukkit/build.gradle index ff36091..2e1ad55 100644 --- a/spark-nukkit/build.gradle +++ b/spark-nukkit/build.gradle @@ -23,7 +23,7 @@ processResources { } shadowJar { - archiveName = 'spark-nukkit.jar' + archiveName = "spark-${project.pluginVersion}-nukkit.jar" relocate 'okio', 'me.lucko.spark.lib.okio' relocate 'okhttp3', 'me.lucko.spark.lib.okhttp3' diff --git a/spark-sponge7/build.gradle b/spark-sponge7/build.gradle index 5b26924..b18ffdb 100644 --- a/spark-sponge7/build.gradle +++ b/spark-sponge7/build.gradle @@ -20,7 +20,7 @@ blossom { } shadowJar { - archiveFileName = 'spark-sponge7.jar' + archiveFileName = "spark-${project.pluginVersion}-sponge7.jar" relocate 'okio', 'me.lucko.spark.lib.okio' relocate 'okhttp3', 'me.lucko.spark.lib.okhttp3' diff --git a/spark-sponge8/build.gradle b/spark-sponge8/build.gradle index 45f17b3..314ab18 100644 --- a/spark-sponge8/build.gradle +++ b/spark-sponge8/build.gradle @@ -22,7 +22,7 @@ processResources { } shadowJar { - archiveFileName = 'spark-sponge8.jar' + archiveFileName = "spark-${project.pluginVersion}-sponge8.jar" dependencies { exclude(dependency('net.kyori:^(?!adventure-text-feature-pagination).+$')) diff --git a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8CommandSender.java b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8CommandSender.java index 5e7a65a..e7878dc 100644 --- a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8CommandSender.java +++ b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8CommandSender.java @@ -17,6 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package me.lucko.spark.sponge; import me.lucko.spark.common.command.sender.AbstractCommandSender; diff --git a/spark-universal/build.gradle b/spark-universal/build.gradle deleted file mode 100644 index f784a9e..0000000 --- a/spark-universal/build.gradle +++ /dev/null @@ -1,33 +0,0 @@ -plugins { - id 'com.github.johnrengelman.shadow' version '7.0.0' -} - -dependencies { - implementation project(':spark-common') - implementation project(':spark-bukkit') - implementation project(':spark-bungeecord') -} - -shadowJar { - archiveName = 'spark.jar' - - relocate 'okio', 'me.lucko.spark.lib.okio' - relocate 'okhttp3', 'me.lucko.spark.lib.okhttp3' - relocate 'net.kyori.adventure', 'me.lucko.spark.lib.adventure' - relocate 'net.kyori.examination', 'me.lucko.spark.lib.adventure.examination' - relocate 'net.bytebuddy', 'me.lucko.spark.lib.bytebuddy' - relocate 'org.tukaani.xz', 'me.lucko.spark.lib.xz' - relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' - relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' - relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' - - exclude 'module-info.class' - exclude 'META-INF/maven/**' - exclude 'META-INF/proguard/**' -} - -artifacts { - archives shadowJar - shadow shadowJar -} - diff --git a/spark-velocity/build.gradle b/spark-velocity/build.gradle index 8a345f9..b2e938b 100644 --- a/spark-velocity/build.gradle +++ b/spark-velocity/build.gradle @@ -20,7 +20,7 @@ blossom { } shadowJar { - archiveName = 'spark-velocity.jar' + archiveName = "spark-${project.pluginVersion}-velocity.jar" dependencies { exclude(dependency('net.kyori:^(?!adventure-text-feature-pagination).+$')) diff --git a/spark-velocity4/build.gradle b/spark-velocity4/build.gradle index e2a0559..5bef80b 100644 --- a/spark-velocity4/build.gradle +++ b/spark-velocity4/build.gradle @@ -25,7 +25,7 @@ blossom { } shadowJar { - archiveName = 'spark-velocity4.jar' + archiveName = "spark-${project.pluginVersion}-velocity4.jar" dependencies { exclude(dependency('net.kyori:^(?!adventure-text-feature-pagination).+$')) diff --git a/spark-waterdog/build.gradle b/spark-waterdog/build.gradle index 07be15c..c11e3fb 100644 --- a/spark-waterdog/build.gradle +++ b/spark-waterdog/build.gradle @@ -28,7 +28,7 @@ processResources { } shadowJar { - archiveName = 'spark-waterdog.jar' + archiveName = "spark-${project.pluginVersion}-waterdog.jar" relocate 'okio', 'me.lucko.spark.lib.okio' relocate 'okhttp3', 'me.lucko.spark.lib.okhttp3' -- cgit From 8a39ad426ca4fe1f5ef39fc5b0b538937802a001 Mon Sep 17 00:00:00 2001 From: Luck Date: Sun, 6 Feb 2022 21:02:28 +0000 Subject: Catch any exception when uploading data (#171) --- .../java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java | 2 +- .../main/java/me/lucko/spark/common/command/modules/SamplerModule.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'spark-common/src/main/java/me/lucko/spark/common/command') diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java index 491ec1e..1030f35 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HeapAnalysisModule.java @@ -111,7 +111,7 @@ public class HeapAnalysisModule implements CommandModule { ); platform.getActivityLog().addToLog(Activity.urlActivity(sender, System.currentTimeMillis(), "Heap dump summary", url)); - } catch (IOException e) { + } catch (Exception e) { resp.broadcastPrefixed(text("An error occurred whilst uploading the data. Attempting to save to disk instead.", RED)); e.printStackTrace(); saveToFile = true; diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java index 26f20e7..970d062 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java @@ -324,7 +324,7 @@ public class SamplerModule implements CommandModule { ); platform.getActivityLog().addToLog(Activity.urlActivity(resp.sender(), System.currentTimeMillis(), "Profiler", url)); - } catch (IOException e) { + } catch (Exception e) { resp.broadcastPrefixed(text("An error occurred whilst uploading the results. Attempting to save to disk instead.", RED)); e.printStackTrace(); saveToFile = true; -- cgit From 45edd6197aa777d0c746f37a813d6be4ca916694 Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 18 Apr 2022 11:54:40 +0100 Subject: Include network interface statistics in '/spark health' command --- .../java/me/lucko/spark/common/SparkPlatform.java | 2 + .../spark/common/command/modules/HealthModule.java | 51 +++- .../lucko/spark/common/monitor/net/Direction.java | 37 +++ .../monitor/net/NetworkInterfaceAverages.java | 88 +++++++ .../common/monitor/net/NetworkInterfaceInfo.java | 274 +++++++++++++++++++++ .../spark/common/monitor/net/NetworkMonitor.java | 140 +++++++++++ .../spark/common/monitor/ping/PingStatistics.java | 5 +- .../platform/PlatformStatisticsProvider.java | 30 +-- .../serverconfig/AbstractServerConfigProvider.java | 2 +- .../me/lucko/spark/common/util/FormatUtil.java | 23 ++ .../java/me/lucko/spark/common/util/LinuxProc.java | 7 +- .../me/lucko/spark/common/util/RollingAverage.java | 11 + spark-common/src/main/proto/spark/spark.proto | 22 +- .../me/lucko/spark/sponge/Sponge7SparkPlugin.java | 2 +- 14 files changed, 667 insertions(+), 27 deletions(-) create mode 100644 spark-common/src/main/java/me/lucko/spark/common/monitor/net/Direction.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceAverages.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkMonitor.java (limited to 'spark-common/src/main/java/me/lucko/spark/common/command') diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java index a087fc9..a961925 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java @@ -40,6 +40,7 @@ import me.lucko.spark.common.command.tabcomplete.CompletionSupplier; import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.memory.GarbageCollectorStatistics; +import me.lucko.spark.common.monitor.net.NetworkMonitor; import me.lucko.spark.common.monitor.ping.PingStatistics; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.monitor.tick.TickStatistics; @@ -166,6 +167,7 @@ public class SparkPlatform { this.pingStatistics.start(); } CpuMonitor.ensureMonitoring(); + NetworkMonitor.ensureMonitoring(); // poll startup GC statistics after plugins & the world have loaded this.plugin.executeAsync(() -> { diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java index ea4f140..ee3592a 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java @@ -29,6 +29,9 @@ import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.command.tabcomplete.TabCompleter; import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.disk.DiskUsage; +import me.lucko.spark.common.monitor.net.Direction; +import me.lucko.spark.common.monitor.net.NetworkInterfaceAverages; +import me.lucko.spark.common.monitor.net.NetworkMonitor; import me.lucko.spark.common.monitor.ping.PingStatistics; import me.lucko.spark.common.monitor.ping.PingSummary; import me.lucko.spark.common.monitor.tick.TickStatistics; @@ -45,6 +48,7 @@ import java.lang.management.MemoryType; import java.lang.management.MemoryUsage; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -81,8 +85,9 @@ public class HealthModule implements CommandModule { consumer.accept(Command.builder() .aliases("healthreport", "health", "ht") .argumentUsage("memory", null) + .argumentUsage("network", null) .executor(HealthModule::healthReport) - .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--memory")) + .tabCompleter((platform, sender, arguments) -> TabCompleter.completeForOpts(arguments, "--memory", "--network")) .build() ); } @@ -197,6 +202,8 @@ public class HealthModule implements CommandModule { addDetailedMemoryStats(report, memoryMXBean); } + addNetworkStats(report, arguments.boolFlag("network")); + addDiskStats(report); resp.reply(report); @@ -352,6 +359,48 @@ public class HealthModule implements CommandModule { } } + private static void addNetworkStats(List report, boolean detailed) { + List averagesReport = new LinkedList<>(); + + for (Map.Entry ent : NetworkMonitor.systemAverages().entrySet()) { + String interfaceName = ent.getKey(); + NetworkInterfaceAverages averages = ent.getValue(); + + for (Direction direction : Direction.values()) { + long bytesPerSec = (long) averages.bytesPerSecond(direction).mean(); + long packetsPerSec = (long) averages.packetsPerSecond(direction).mean(); + + if (detailed || bytesPerSec > 0 || packetsPerSec > 0) { + averagesReport.add(text() + .color(GRAY) + .content(" ") + .append(FormatUtil.formatBytes(bytesPerSec, GREEN, "/s")) + .append(text(" / ")) + .append(text(String.format("%,d", packetsPerSec), WHITE)) + .append(text(" pps ")) + .append(text().color(DARK_GRAY) + .append(text('(')) + .append(text(interfaceName + " " + direction.abbrev(), WHITE)) + .append(text(')')) + ) + .build() + ); + } + } + } + + if (!averagesReport.isEmpty()) { + report.add(text() + .append(text(">", DARK_GRAY, BOLD)) + .append(space()) + .append(text("Network usage: (system, last 15m)", GOLD)) + .build() + ); + report.addAll(averagesReport); + report.add(empty()); + } + } + private static void addDiskStats(List report) { long total = DiskUsage.getTotal(); long used = DiskUsage.getUsed(); diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/net/Direction.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/Direction.java new file mode 100644 index 0000000..d4d11ff --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/Direction.java @@ -0,0 +1,37 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.monitor.net; + +public enum Direction { + + RECEIVE("rx"), + TRANSMIT("tx"); + + private final String abbreviation; + + Direction(String abbreviation) { + this.abbreviation = abbreviation; + } + + public String abbrev() { + return this.abbreviation; + } +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceAverages.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceAverages.java new file mode 100644 index 0000000..0ce0639 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceAverages.java @@ -0,0 +1,88 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.monitor.net; + +import me.lucko.spark.common.util.RollingAverage; + +import java.math.BigDecimal; + +public final class NetworkInterfaceAverages { + private final RollingAverage rxBytesPerSecond; + private final RollingAverage txBytesPerSecond; + private final RollingAverage rxPacketsPerSecond; + private final RollingAverage txPacketsPerSecond; + + NetworkInterfaceAverages(int windowSize) { + this.rxBytesPerSecond = new RollingAverage(windowSize); + this.txBytesPerSecond = new RollingAverage(windowSize); + this.rxPacketsPerSecond = new RollingAverage(windowSize); + this.txPacketsPerSecond = new RollingAverage(windowSize); + } + + void accept(NetworkInterfaceInfo info, RateCalculator rateCalculator) { + this.rxBytesPerSecond.add(rateCalculator.calculate(info.getReceivedBytes())); + this.txBytesPerSecond.add(rateCalculator.calculate(info.getTransmittedBytes())); + this.rxPacketsPerSecond.add(rateCalculator.calculate(info.getReceivedPackets())); + this.txPacketsPerSecond.add(rateCalculator.calculate(info.getTransmittedPackets())); + } + + interface RateCalculator { + BigDecimal calculate(long value); + } + + public RollingAverage bytesPerSecond(Direction direction) { + switch (direction) { + case RECEIVE: + return rxBytesPerSecond(); + case TRANSMIT: + return txBytesPerSecond(); + default: + throw new AssertionError(); + } + } + + public RollingAverage packetsPerSecond(Direction direction) { + switch (direction) { + case RECEIVE: + return rxPacketsPerSecond(); + case TRANSMIT: + return txPacketsPerSecond(); + default: + throw new AssertionError(); + } + } + + public RollingAverage rxBytesPerSecond() { + return this.rxBytesPerSecond; + } + + public RollingAverage rxPacketsPerSecond() { + return this.rxPacketsPerSecond; + } + + public RollingAverage txBytesPerSecond() { + return this.txBytesPerSecond; + } + + public RollingAverage txPacketsPerSecond() { + return this.txPacketsPerSecond; + } +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java new file mode 100644 index 0000000..6ec700b --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java @@ -0,0 +1,274 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.monitor.net; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.spark.common.util.LinuxProc; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +/** + * Exposes information/statistics about a network interface. + */ +public final class NetworkInterfaceInfo { + public static final NetworkInterfaceInfo ZERO = new NetworkInterfaceInfo("", 0, 0, 0, 0, 0, 0); + + private final String name; + private final long rxBytes; + private final long rxPackets; + private final long rxErrors; + private final long txBytes; + private final long txPackets; + private final long txErrors; + + public NetworkInterfaceInfo(String name, long rxBytes, long rxPackets, long rxErrors, long txBytes, long txPackets, long txErrors) { + this.name = name; + this.rxBytes = rxBytes; + this.rxPackets = rxPackets; + this.rxErrors = rxErrors; + this.txBytes = txBytes; + this.txPackets = txPackets; + this.txErrors = txErrors; + } + + /** + * Gets the name of the network interface. + * + * @return the interface name + */ + public String getName() { + return this.name; + } + + /** + * Gets the total number of bytes of data received by the interface. + * + * @return the total received bytes + */ + public long getReceivedBytes() { + return this.rxBytes; + } + + /** + * Gets the total number of packets of data received by the interface. + * + * @return the total received packets + */ + public long getReceivedPackets() { + return this.rxPackets; + } + + /** + * Gets the total number of receive errors detected by the device driver. + * + * @return the total receive errors + */ + public long getReceiveErrors() { + return this.rxErrors; + } + + /** + * Gets the total number of bytes of data transmitted by the interface. + * + * @return the total transmitted bytes + */ + public long getTransmittedBytes() { + return this.txBytes; + } + + /** + * Gets the total number of packets of data transmitted by the interface. + * + * @return the total transmitted packets + */ + public long getTransmittedPackets() { + return this.txPackets; + } + + /** + * Gets the total number of transmit errors detected by the device driver. + * + * @return the total transmit errors + */ + public long getTransmitErrors() { + return this.txErrors; + } + + public long getBytes(Direction direction) { + switch (direction) { + case RECEIVE: + return getReceivedBytes(); + case TRANSMIT: + return getTransmittedBytes(); + default: + throw new AssertionError(); + } + } + + public long getPackets(Direction direction) { + switch (direction) { + case RECEIVE: + return getReceivedPackets(); + case TRANSMIT: + return getTransmittedPackets(); + default: + throw new AssertionError(); + } + } + + public boolean isZero() { + return this.rxBytes == 0 && this.rxPackets == 0 && this.rxErrors == 0 && + this.txBytes == 0 && this.txPackets == 0 && this.txErrors == 0; + } + + public NetworkInterfaceInfo subtract(NetworkInterfaceInfo other) { + if (other == ZERO || other.isZero()) { + return this; + } + + return new NetworkInterfaceInfo( + this.name, + this.rxBytes - other.rxBytes, + this.rxPackets - other.rxPackets, + this.rxErrors - other.rxErrors, + this.txBytes - other.txBytes, + this.txPackets - other.txPackets, + this.txErrors - other.txErrors + ); + } + + /** + * Calculate the difference between two readings in order to calculate the rate. + * + * @param current the polled values + * @param previous the previously polled values + * @return the difference + */ + public static @NonNull Map difference(Map current, Map previous) { + if (previous == null || previous.isEmpty()) { + return current; + } + + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (NetworkInterfaceInfo netInf : current.values()) { + String name = netInf.getName(); + builder.put(name, netInf.subtract(previous.getOrDefault(name, ZERO))); + } + return builder.build(); + } + + /** + * Queries the network interface statistics for the system. + * + *

Returns an empty {@link Map} if no statistics could be gathered.

+ * + * @return the system net stats + */ + public static @NonNull Map pollSystem() { + try { + List output = LinuxProc.NET_DEV.read(); + return read(output); + } catch (Exception e) { + return Collections.emptyMap(); + } + } + + private static final Pattern PROC_NET_DEV_PATTERN = Pattern.compile("^\\s+(\\w+):([\\d\\s]+)$"); + + private static @NonNull Map read(List output) { + // Inter-| Receive | Transmit + // face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed + // lo: 2776770 11307 0 0 0 0 0 0 2776770 11307 0 0 0 0 0 0 + // eth0: 1215645 2751 0 0 0 0 0 0 1782404 4324 0 0 0 427 0 0 + // ppp0: 1622270 5552 1 0 0 0 0 0 354130 5669 0 0 0 0 0 0 + // tap0: 7714 81 0 0 0 0 0 0 7714 81 0 0 0 0 0 0 + + if (output.size() < 3) { + // no data + return Collections.emptyMap(); + } + + String header = output.get(1); + String[] categories = header.split("\\|"); + if (categories.length != 3) { + // unknown format + return Collections.emptyMap(); + } + + List rxFields = Arrays.asList(categories[1].trim().split("\\s+")); + List txFields = Arrays.asList(categories[2].trim().split("\\s+")); + + int rxFieldsLength = rxFields.size(); + int txFieldsLength = txFields.size(); + + int fieldRxBytes = rxFields.indexOf("bytes"); + int fieldRxPackets = rxFields.indexOf("packets"); + int fieldRxErrors = rxFields.indexOf("errs"); + + int fieldTxBytes = rxFieldsLength + txFields.indexOf("bytes"); + int fieldTxPackets = rxFieldsLength + txFields.indexOf("packets"); + int fieldTxErrors = rxFieldsLength + txFields.indexOf("errs"); + + int expectedFields = rxFieldsLength + txFieldsLength; + + if (IntStream.of(fieldRxBytes, fieldRxPackets, fieldRxErrors, fieldTxBytes, fieldTxPackets, fieldTxErrors).anyMatch(i -> i == -1)) { + // missing required fields + return Collections.emptyMap(); + } + + ImmutableMap.Builder builder = ImmutableMap.builder(); + + for (String line : output.subList(2, output.size())) { + Matcher matcher = PROC_NET_DEV_PATTERN.matcher(line); + if (matcher.matches()) { + String interfaceName = matcher.group(1); + String[] stringValues = matcher.group(2).trim().split("\\s+"); + + if (stringValues.length != expectedFields) { + continue; + } + + long[] values = Arrays.stream(stringValues).mapToLong(Long::parseLong).toArray(); + builder.put(interfaceName, new NetworkInterfaceInfo( + interfaceName, + values[fieldRxBytes], + values[fieldRxPackets], + values[fieldRxErrors], + values[fieldTxBytes], + values[fieldTxPackets], + values[fieldTxErrors] + )); + } + } + + return builder.build(); + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkMonitor.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkMonitor.java new file mode 100644 index 0000000..dadd4e5 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkMonitor.java @@ -0,0 +1,140 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.lucko.spark.common.monitor.net; + +import me.lucko.spark.common.monitor.MonitoringExecutor; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +/** + * Exposes and monitors the system/process network usage. + */ +public enum NetworkMonitor { + ; + + // Latest readings + private static final AtomicReference> SYSTEM = new AtomicReference<>(); + + // a pattern to match the interface names to calculate rolling averages for + private static final Pattern INTERFACES_TO_MONITOR = Pattern.compile("^(eth\\d+)|(lo)$"); + + // Rolling averages for system/process data over 15 mins + private static final Map SYSTEM_AVERAGES = new ConcurrentHashMap<>(); + + // poll every minute, keep rolling averages for 15 mins + private static final int POLL_INTERVAL = 60; + private static final int WINDOW_SIZE_SECONDS = (int) TimeUnit.MINUTES.toSeconds(15); + private static final int WINDOW_SIZE = WINDOW_SIZE_SECONDS / POLL_INTERVAL; // 15 + + static { + // schedule rolling average calculations. + MonitoringExecutor.INSTANCE.scheduleAtFixedRate(new RollingAverageCollectionTask(), 1, POLL_INTERVAL, TimeUnit.SECONDS); + } + + /** + * Ensures that the static initializer has been called. + */ + @SuppressWarnings("EmptyMethod") + public static void ensureMonitoring() { + // intentionally empty + } + + public static Map systemTotals() { + Map values = SYSTEM.get(); + return values == null ? Collections.emptyMap() : values; + } + + public static Map systemAverages() { + return Collections.unmodifiableMap(SYSTEM_AVERAGES); + } + + /** + * Task to poll network activity and add to the rolling averages in the enclosing class. + */ + private static final class RollingAverageCollectionTask implements Runnable { + private static final BigDecimal POLL_INTERVAL_DECIMAL = BigDecimal.valueOf(POLL_INTERVAL); + + @Override + public void run() { + Map values = pollAndDiff(NetworkInterfaceInfo::pollSystem, SYSTEM); + if (values != null) { + submit(SYSTEM_AVERAGES, values); + } + } + + /** + * Submits the incoming values into the rolling averages map. + * + * @param values the values + */ + private static void submit(Map rollingAveragesMap, Map values) { + // ensure all incoming keys are present in the rolling averages map + for (String key : values.keySet()) { + if (INTERFACES_TO_MONITOR.matcher(key).matches()) { + rollingAveragesMap.computeIfAbsent(key, k -> new NetworkInterfaceAverages(WINDOW_SIZE)); + } + } + + // submit a value (0 if unknown) to each rolling average instance in the map + for (Map.Entry entry : rollingAveragesMap.entrySet()) { + String interfaceName = entry.getKey(); + NetworkInterfaceAverages rollingAvgs = entry.getValue(); + + NetworkInterfaceInfo info = values.getOrDefault(interfaceName, NetworkInterfaceInfo.ZERO); + rollingAvgs.accept(info, RollingAverageCollectionTask::calculateRate); + } + } + + private static BigDecimal calculateRate(long value) { + return BigDecimal.valueOf(value).divide(POLL_INTERVAL_DECIMAL, RoundingMode.HALF_UP); + } + + private static Map pollAndDiff(Supplier> poller, AtomicReference> valueReference) { + // poll the latest value from the supplier + Map latest = poller.get(); + + // update the value ref. + // if the previous value was null, and the new value is empty, keep it null + Map previous = valueReference.getAndUpdate(prev -> { + if (prev == null && latest.isEmpty()) { + return null; + } else { + return latest; + } + }); + + if (previous == null) { + return null; + } + + return NetworkInterfaceInfo.difference(latest, previous); + } + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java index 8b5b5b3..49fcbe1 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/ping/PingStatistics.java @@ -34,13 +34,14 @@ import java.util.concurrent.TimeUnit; * Provides statistics for player ping RTT to the server. */ public final class PingStatistics implements Runnable, AutoCloseable { - private static final int WINDOW_SIZE_SECONDS = (int) TimeUnit.MINUTES.toSeconds(15); private static final int QUERY_RATE_SECONDS = 10; + private static final int WINDOW_SIZE_SECONDS = (int) TimeUnit.MINUTES.toSeconds(15); // 900 + private static final int WINDOW_SIZE = WINDOW_SIZE_SECONDS / QUERY_RATE_SECONDS; // 90 /** The platform function that provides player ping times */ private final PlayerPingProvider provider; /** Rolling average of the median ping across all players */ - private final RollingAverage rollingAverage = new RollingAverage(WINDOW_SIZE_SECONDS / QUERY_RATE_SECONDS); + private final RollingAverage rollingAverage = new RollingAverage(WINDOW_SIZE); /** The scheduler task that polls pings and calculates the rolling average */ private ScheduledFuture future; diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java index 5b8d1d4..046def7 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java @@ -26,9 +26,10 @@ import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.disk.DiskUsage; import me.lucko.spark.common.monitor.memory.GarbageCollectorStatistics; import me.lucko.spark.common.monitor.memory.MemoryInfo; +import me.lucko.spark.common.monitor.net.NetworkInterfaceAverages; +import me.lucko.spark.common.monitor.net.NetworkMonitor; import me.lucko.spark.common.monitor.ping.PingStatistics; import me.lucko.spark.common.monitor.tick.TickStatistics; -import me.lucko.spark.common.util.RollingAverage; import me.lucko.spark.proto.SparkProtos.PlatformStatistics; import me.lucko.spark.proto.SparkProtos.SystemStatistics; @@ -108,6 +109,17 @@ public class PlatformStatisticsProvider { .build() )); + Map networkInterfaceStats = NetworkMonitor.systemAverages(); + networkInterfaceStats.forEach((name, statistics) -> builder.putNet( + name, + SystemStatistics.NetInterface.newBuilder() + .setRxBytesPerSecond(statistics.rxBytesPerSecond().toProto()) + .setRxPacketsPerSecond(statistics.rxPacketsPerSecond().toProto()) + .setTxBytesPerSecond(statistics.txBytesPerSecond().toProto()) + .setTxPacketsPerSecond(statistics.txPacketsPerSecond().toProto()) + .build() + )); + return builder.build(); } @@ -149,8 +161,8 @@ public class PlatformStatisticsProvider { ); if (tickStatistics.isDurationSupported()) { builder.setMspt(PlatformStatistics.Mspt.newBuilder() - .setLast1M(rollingAverageValues(tickStatistics.duration1Min())) - .setLast5M(rollingAverageValues(tickStatistics.duration5Min())) + .setLast1M(tickStatistics.duration1Min().toProto()) + .setLast5M(tickStatistics.duration5Min().toProto()) .build() ); } @@ -159,7 +171,7 @@ public class PlatformStatisticsProvider { PingStatistics pingStatistics = this.platform.getPingStatistics(); if (pingStatistics != null && pingStatistics.getPingAverage().getSamples() != 0) { builder.setPing(PlatformStatistics.Ping.newBuilder() - .setLast15M(rollingAverageValues(pingStatistics.getPingAverage())) + .setLast15M(pingStatistics.getPingAverage().toProto()) .build() ); } @@ -167,14 +179,4 @@ public class PlatformStatisticsProvider { return builder.build(); } - private static PlatformStatistics.RollingAverageValues rollingAverageValues(RollingAverage rollingAverage) { - return PlatformStatistics.RollingAverageValues.newBuilder() - .setMean(rollingAverage.mean()) - .setMax(rollingAverage.max()) - .setMin(rollingAverage.min()) - .setMedian(rollingAverage.median()) - .setPercentile95(rollingAverage.percentile95th()) - .build(); - } - } diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java index d9e8bf4..7e04400 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java @@ -34,7 +34,7 @@ import java.util.Map; /** * Abstract implementation of {@link ServerConfigProvider}. * - *

THis implementation is able to delete hidden paths from + *

This implementation is able to delete hidden paths from * the configurations before they are sent to the viewer.

* * @param the file type diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java b/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java index 492d4ea..cbc496d 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java @@ -20,6 +20,9 @@ package me.lucko.spark.common.util; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; + public enum FormatUtil { ; @@ -37,4 +40,24 @@ public enum FormatUtil { int sizeIndex = (int) (Math.log(bytes) / Math.log(1024)); return String.format("%.1f", bytes / Math.pow(1024, sizeIndex)) + " " + SIZE_UNITS[sizeIndex]; } + + public static Component formatBytes(long bytes, TextColor color, String suffix) { + String value; + String unit; + + if (bytes == 0) { + value = "0"; + unit = "KB" + suffix; + } else { + int sizeIndex = (int) (Math.log(bytes) / Math.log(1024)); + value = String.format("%.1f", bytes / Math.pow(1024, sizeIndex)); + unit = SIZE_UNITS[sizeIndex] + suffix; + } + + return Component.text() + .append(Component.text(value, color)) + .append(Component.space()) + .append(Component.text(unit)) + .build(); + } } diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java b/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java index 0926ae7..7d688d7 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java @@ -44,7 +44,12 @@ public enum LinuxProc { /** * Information about the system memory. */ - MEMINFO("/proc/meminfo"); + MEMINFO("/proc/meminfo"), + + /** + * Information about the system network usage. + */ + NET_DEV("/proc/net/dev"); private final Path path; diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java b/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java index 57dfdff..65753bc 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java @@ -21,6 +21,7 @@ package me.lucko.spark.common.util; import me.lucko.spark.api.statistic.misc.DoubleAverageInfo; +import me.lucko.spark.proto.SparkProtos; import java.math.BigDecimal; import java.math.RoundingMode; @@ -111,4 +112,14 @@ public class RollingAverage implements DoubleAverageInfo { return sortedSamples[rank].doubleValue(); } + public SparkProtos.RollingAverageValues toProto() { + return SparkProtos.RollingAverageValues.newBuilder() + .setMean(mean()) + .setMax(max()) + .setMin(min()) + .setMedian(median()) + .setPercentile95(percentile95th()) + .build(); + } + } diff --git a/spark-common/src/main/proto/spark/spark.proto b/spark-common/src/main/proto/spark/spark.proto index d800762..95eaacd 100644 --- a/spark-common/src/main/proto/spark/spark.proto +++ b/spark-common/src/main/proto/spark/spark.proto @@ -31,6 +31,7 @@ message SystemStatistics { Os os = 5; Java java = 6; int64 uptime = 7; + map net = 8; message Cpu { int32 threads = 1; @@ -77,6 +78,13 @@ message SystemStatistics { string vendor_version = 3; string vm_args = 4; } + + message NetInterface { + RollingAverageValues rx_bytes_per_second = 1; + RollingAverageValues tx_bytes_per_second = 2; + RollingAverageValues rx_packets_per_second = 3; + RollingAverageValues tx_packets_per_second = 4; + } } message PlatformStatistics { @@ -116,14 +124,14 @@ message PlatformStatistics { message Ping { RollingAverageValues last15m = 1; } +} - message RollingAverageValues { - double mean = 1; - double max = 2; - double min = 3; - double median = 4; - double percentile95 = 5; - } +message RollingAverageValues { + double mean = 1; + double max = 2; + double min = 3; + double median = 4; + double percentile95 = 5; } message CommandSenderMetadata { diff --git a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java index dc34949..670e0c5 100644 --- a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java +++ b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java @@ -29,8 +29,8 @@ import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; - import me.lucko.spark.common.util.ClassSourceLookup; + import org.slf4j.Logger; import org.spongepowered.api.Game; import org.spongepowered.api.command.CommandCallable; -- cgit From 86a312842662ba57faa8fd5e8471672dfac13089 Mon Sep 17 00:00:00 2001 From: Luck Date: Tue, 19 Apr 2022 12:45:46 +0100 Subject: Fix /proc/net output parsing and number formatting (#195) --- .../me/lucko/spark/common/command/modules/HealthModule.java | 3 ++- .../lucko/spark/common/monitor/net/NetworkInterfaceInfo.java | 2 +- .../src/main/java/me/lucko/spark/common/util/FormatUtil.java | 10 ++++++---- .../java/me/lucko/spark/common/util/StatisticFormatter.java | 3 ++- 4 files changed, 11 insertions(+), 7 deletions(-) (limited to 'spark-common/src/main/java/me/lucko/spark/common/command') diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java index ee3592a..16eadc8 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java +++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/HealthModule.java @@ -48,6 +48,7 @@ import java.lang.management.MemoryType; import java.lang.management.MemoryUsage; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -376,7 +377,7 @@ public class HealthModule implements CommandModule { .content(" ") .append(FormatUtil.formatBytes(bytesPerSec, GREEN, "/s")) .append(text(" / ")) - .append(text(String.format("%,d", packetsPerSec), WHITE)) + .append(text(String.format(Locale.ENGLISH, "%,d", packetsPerSec), WHITE)) .append(text(" pps ")) .append(text().color(DARK_GRAY) .append(text('(')) diff --git a/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java index 6ec700b..bd9e187 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java +++ b/spark-common/src/main/java/me/lucko/spark/common/monitor/net/NetworkInterfaceInfo.java @@ -200,7 +200,7 @@ public final class NetworkInterfaceInfo { } } - private static final Pattern PROC_NET_DEV_PATTERN = Pattern.compile("^\\s+(\\w+):([\\d\\s]+)$"); + private static final Pattern PROC_NET_DEV_PATTERN = Pattern.compile("^\\s*(\\w+):([\\d\\s]+)$"); private static @NonNull Map read(List output) { // Inter-| Receive | Transmit diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java b/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java index cbc496d..c4a3d66 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java @@ -23,6 +23,8 @@ package me.lucko.spark.common.util; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; +import java.util.Locale; + public enum FormatUtil { ; @@ -34,23 +36,23 @@ public enum FormatUtil { } public static String formatBytes(long bytes) { - if (bytes == 0) { + if (bytes <= 0) { return "0 bytes"; } int sizeIndex = (int) (Math.log(bytes) / Math.log(1024)); - return String.format("%.1f", bytes / Math.pow(1024, sizeIndex)) + " " + SIZE_UNITS[sizeIndex]; + return String.format(Locale.ENGLISH, "%.1f", bytes / Math.pow(1024, sizeIndex)) + " " + SIZE_UNITS[sizeIndex]; } public static Component formatBytes(long bytes, TextColor color, String suffix) { String value; String unit; - if (bytes == 0) { + if (bytes <= 0) { value = "0"; unit = "KB" + suffix; } else { int sizeIndex = (int) (Math.log(bytes) / Math.log(1024)); - value = String.format("%.1f", bytes / Math.pow(1024, sizeIndex)); + value = String.format(Locale.ENGLISH, "%.1f", bytes / Math.pow(1024, sizeIndex)); unit = SIZE_UNITS[sizeIndex] + suffix; } diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java b/spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java index 24e49fa..22ee9bb 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java @@ -27,6 +27,7 @@ import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.TextColor; import java.lang.management.MemoryUsage; +import java.util.Locale; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY; @@ -76,7 +77,7 @@ public enum StatisticFormatter { color = GREEN; } - return text(String.format("%.1f", duration), color); + return text(String.format(Locale.ENGLISH, "%.1f", duration), color); } public static TextComponent formatCpuUsage(double usage) { -- cgit