|
|
|
|
@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
diff --git a/build.gradle.kts b/build.gradle.kts
|
|
|
|
|
index 9cf389defdaeb887e9cad4f0fed3f3b95667b238..3c8293f002f11b430083502362fdc801f44aa138 100644
|
|
|
|
|
index 9cf389defdaeb887e9cad4f0fed3f3b95667b238..b41b186397d013c19436c345be98b785d4bd0295 100644
|
|
|
|
|
--- a/build.gradle.kts
|
|
|
|
|
+++ b/build.gradle.kts
|
|
|
|
|
@@ -7,8 +7,12 @@ plugins {
|
|
|
|
|
@@ -38,7 +38,7 @@ index 9cf389defdaeb887e9cad4f0fed3f3b95667b238..3c8293f002f11b430083502362fdc801
|
|
|
|
|
// Paper start
|
|
|
|
|
implementation("org.jline:jline-terminal-jansi:3.21.0")
|
|
|
|
|
implementation("net.minecrell:terminalconsoleappender:1.3.0")
|
|
|
|
|
@@ -42,6 +46,14 @@ dependencies {
|
|
|
|
|
@@ -42,6 +46,13 @@ dependencies {
|
|
|
|
|
runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3")
|
|
|
|
|
runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.3")
|
|
|
|
|
|
|
|
|
|
@@ -48,12 +48,11 @@ index 9cf389defdaeb887e9cad4f0fed3f3b95667b238..3c8293f002f11b430083502362fdc801
|
|
|
|
|
+ exclude(group="org.yaml", module="snakeyaml")
|
|
|
|
|
+ }
|
|
|
|
|
+ // Pufferfish end
|
|
|
|
|
+ implementation("com.github.technove:Flare:34637f3f87") // Pufferfish - flare
|
|
|
|
|
+
|
|
|
|
|
testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test
|
|
|
|
|
testImplementation("junit:junit:4.13.2")
|
|
|
|
|
testImplementation("org.hamcrest:hamcrest-library:1.3")
|
|
|
|
|
@@ -50,6 +62,14 @@ dependencies {
|
|
|
|
|
@@ -50,6 +61,14 @@ dependencies {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val craftbukkitPackageVersion = "1_19_R3" // Paper
|
|
|
|
|
@@ -68,7 +67,7 @@ index 9cf389defdaeb887e9cad4f0fed3f3b95667b238..3c8293f002f11b430083502362fdc801
|
|
|
|
|
tasks.jar {
|
|
|
|
|
archiveClassifier.set("dev")
|
|
|
|
|
|
|
|
|
|
@@ -62,7 +82,7 @@ tasks.jar {
|
|
|
|
|
@@ -62,7 +81,7 @@ tasks.jar {
|
|
|
|
|
attributes(
|
|
|
|
|
"Main-Class" to "org.bukkit.craftbukkit.Main",
|
|
|
|
|
"Implementation-Title" to "CraftBukkit",
|
|
|
|
|
@@ -617,10 +616,10 @@ index 0000000000000000000000000000000000000000..020368da69b9a492155f6de6297f7473
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..e0076044d0a29e33ee297fe6180a041436075297
|
|
|
|
|
index 0000000000000000000000000000000000000000..6e441a1a28ba72a8b1cc09fe5fca71b3c70627d4
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
|
|
|
|
|
@@ -0,0 +1,317 @@
|
|
|
|
|
@@ -0,0 +1,285 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish;
|
|
|
|
|
+
|
|
|
|
|
+import gg.pufferfish.pufferfish.simd.SIMDDetection;
|
|
|
|
|
@@ -638,7 +637,6 @@ index 0000000000000000000000000000000000000000..e0076044d0a29e33ee297fe6180a0414
|
|
|
|
|
+import java.lang.reflect.Method;
|
|
|
|
|
+import java.lang.reflect.Modifier;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import gg.pufferfish.pufferfish.flare.FlareCommand;
|
|
|
|
|
+import net.minecraft.server.MinecraftServer;
|
|
|
|
|
+import org.apache.logging.log4j.Level;
|
|
|
|
|
+import org.bukkit.configuration.ConfigurationSection;
|
|
|
|
|
@@ -647,13 +645,6 @@ index 0000000000000000000000000000000000000000..e0076044d0a29e33ee297fe6180a0414
|
|
|
|
|
+import org.simpleyaml.configuration.comments.CommentType;
|
|
|
|
|
+import org.simpleyaml.configuration.file.YamlFile;
|
|
|
|
|
+import org.simpleyaml.exceptions.InvalidConfigurationException;
|
|
|
|
|
+import org.bukkit.command.SimpleCommandMap;
|
|
|
|
|
+
|
|
|
|
|
+import java.lang.reflect.Method;
|
|
|
|
|
+import java.lang.reflect.Modifier;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.net.URI;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+
|
|
|
|
|
+public class PufferfishConfig {
|
|
|
|
|
+
|
|
|
|
|
@@ -904,30 +895,6 @@ index 0000000000000000000000000000000000000000..e0076044d0a29e33ee297fe6180a0414
|
|
|
|
|
+ "This can improve performance by a few percent, but has minor gameplay implications.");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static URI profileWebUrl;
|
|
|
|
|
+ private static void profilerOptions() {
|
|
|
|
|
+ profileWebUrl = URI.create(getString("flare.url", "https://flare.airplane.gg", "Sets the server to use for profiles."));
|
|
|
|
|
+
|
|
|
|
|
+ setComment("flare", "Configures Flare, the built-in profiler");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ public static String accessToken;
|
|
|
|
|
+ private static void airplaneWebServices() {
|
|
|
|
|
+ accessToken = getString("web-services.token", "");
|
|
|
|
|
+ // todo lookup token (off-thread) and let users know if their token is valid
|
|
|
|
|
+ if (accessToken.length() > 0) {
|
|
|
|
|
+ gg.pufferfish.pufferfish.flare.FlareSetup.init(); // Pufferfish
|
|
|
|
|
+ SimpleCommandMap commandMap = MinecraftServer.getServer().server.getCommandMap();
|
|
|
|
|
+ if (commandMap.getCommand("flare") == null) {
|
|
|
|
|
+ commandMap.register("flare", "Pufferfish", new FlareCommand());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setComment("web-services", "Options for connecting to Pufferfish/Airplane's online utilities");
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ public static boolean disableMethodProfiler;
|
|
|
|
|
+ public static boolean disableOutOfOrderChat;
|
|
|
|
|
@@ -1103,699 +1070,6 @@ index 0000000000000000000000000000000000000000..e877921370f6009a4bd204d9b17d2d58
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
\ No newline at end of file
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/compat/ServerConfigurations.java b/src/main/java/gg/pufferfish/pufferfish/compat/ServerConfigurations.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..4ad189d52b27560424ddb311d0817a334637dc95
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/compat/ServerConfigurations.java
|
|
|
|
|
@@ -0,0 +1,78 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish.compat;
|
|
|
|
|
+
|
|
|
|
|
+import co.aikar.timings.TimingsManager;
|
|
|
|
|
+import com.google.common.io.Files;
|
|
|
|
|
+import org.bukkit.configuration.InvalidConfigurationException;
|
|
|
|
|
+import org.bukkit.configuration.file.YamlConfiguration;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.FileInputStream;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Properties;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+public class ServerConfigurations {
|
|
|
|
|
+
|
|
|
|
|
+ public static final String[] configurationFiles = new String[]{
|
|
|
|
|
+ "server.properties",
|
|
|
|
|
+ "bukkit.yml",
|
|
|
|
|
+ "spigot.yml",
|
|
|
|
|
+ // "paper.yml", // TODO: Figure out what to do with this.
|
|
|
|
|
+ "pufferfish.yml"
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ public static Map<String, String> getCleanCopies() throws IOException {
|
|
|
|
|
+ Map<String, String> files = new HashMap<>(configurationFiles.length);
|
|
|
|
|
+ for (String file : configurationFiles) {
|
|
|
|
|
+ files.put(file, getCleanCopy(file));
|
|
|
|
|
+ }
|
|
|
|
|
+ return files;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static String getCleanCopy(String configName) throws IOException {
|
|
|
|
|
+ File file = new File(configName);
|
|
|
|
|
+ List<String> hiddenConfigs = TimingsManager.hiddenConfigs;
|
|
|
|
|
+
|
|
|
|
|
+ switch (Files.getFileExtension(configName)) {
|
|
|
|
|
+ case "properties": {
|
|
|
|
|
+ Properties properties = new Properties();
|
|
|
|
|
+ try (FileInputStream inputStream = new FileInputStream(file)) {
|
|
|
|
|
+ properties.load(inputStream);
|
|
|
|
|
+ }
|
|
|
|
|
+ for (String hiddenConfig : hiddenConfigs) {
|
|
|
|
|
+ properties.remove(hiddenConfig);
|
|
|
|
|
+ }
|
|
|
|
|
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
|
|
|
+ properties.store(outputStream, "");
|
|
|
|
|
+ return Arrays.stream(outputStream.toString()
|
|
|
|
|
+ .split("\n"))
|
|
|
|
|
+ .filter(line -> !line.startsWith("#"))
|
|
|
|
|
+ .collect(Collectors.joining("\n"));
|
|
|
|
|
+ }
|
|
|
|
|
+ case "yml": {
|
|
|
|
|
+ YamlConfiguration configuration = new YamlConfiguration();
|
|
|
|
|
+ try {
|
|
|
|
|
+ configuration.load(file);
|
|
|
|
|
+ } catch (InvalidConfigurationException e) {
|
|
|
|
|
+ throw new IOException(e);
|
|
|
|
|
+ }
|
|
|
|
|
+ configuration.options().header(null);
|
|
|
|
|
+ for (String key : configuration.getKeys(true)) {
|
|
|
|
|
+ if (hiddenConfigs.contains(key)) {
|
|
|
|
|
+ configuration.set(key, null);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return configuration.saveToString();
|
|
|
|
|
+ }
|
|
|
|
|
+ default:
|
|
|
|
|
+ throw new IllegalArgumentException("Bad file type " + configName);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/flare/CustomCategories.java b/src/main/java/gg/pufferfish/pufferfish/flare/CustomCategories.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..401b42e29bccb5251684062f10b2e0f8b091bc95
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/flare/CustomCategories.java
|
|
|
|
|
@@ -0,0 +1,8 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish.flare;
|
|
|
|
|
+
|
|
|
|
|
+import co.technove.flare.live.category.GraphCategory;
|
|
|
|
|
+
|
|
|
|
|
+public class CustomCategories {
|
|
|
|
|
+ public static final GraphCategory MC_PERF = new GraphCategory("MC Performance");
|
|
|
|
|
+ public static final GraphCategory ENTITIES_AND_CHUNKS = new GraphCategory("Entities & Chunks");
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/flare/FlareCommand.java b/src/main/java/gg/pufferfish/pufferfish/flare/FlareCommand.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..3785d1512eb650f91d58903672c059e7449598fc
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/flare/FlareCommand.java
|
|
|
|
|
@@ -0,0 +1,136 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish.flare;
|
|
|
|
|
+
|
|
|
|
|
+import co.technove.flare.exceptions.UserReportableException;
|
|
|
|
|
+import co.technove.flare.internal.profiling.ProfileType;
|
|
|
|
|
+import gg.pufferfish.pufferfish.PufferfishConfig;
|
|
|
|
|
+import net.kyori.adventure.text.Component;
|
|
|
|
|
+import net.kyori.adventure.text.event.ClickEvent;
|
|
|
|
|
+import net.kyori.adventure.text.format.NamedTextColor;
|
|
|
|
|
+import net.kyori.adventure.text.format.TextColor;
|
|
|
|
|
+import net.kyori.adventure.text.format.TextDecoration;
|
|
|
|
|
+import net.minecraft.server.MinecraftServer;
|
|
|
|
|
+import org.apache.logging.log4j.Level;
|
|
|
|
|
+import org.bukkit.Bukkit;
|
|
|
|
|
+import org.bukkit.command.Command;
|
|
|
|
|
+import org.bukkit.command.CommandSender;
|
|
|
|
|
+import org.bukkit.command.ConsoleCommandSender;
|
|
|
|
|
+import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin;
|
|
|
|
|
+import org.bukkit.util.StringUtil;
|
|
|
|
|
+import org.jetbrains.annotations.NotNull;
|
|
|
|
|
+
|
|
|
|
|
+import java.time.Duration;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+public class FlareCommand extends Command {
|
|
|
|
|
+
|
|
|
|
|
+ private static final String BASE_URL = "https://blog.airplane.gg/flare-tutorial/#setting-the-access-token";
|
|
|
|
|
+ private static final TextColor HEX = TextColor.fromHexString("#e3eaea");
|
|
|
|
|
+ private static final Component PREFIX = Component.text()
|
|
|
|
|
+ .append(Component.text("Flare ✈")
|
|
|
|
|
+ .color(TextColor.fromHexString("#6a7eda"))
|
|
|
|
|
+ .decoration(TextDecoration.BOLD, true)
|
|
|
|
|
+ .append(Component.text(" ", HEX)
|
|
|
|
|
+ .decoration(TextDecoration.BOLD, false)))
|
|
|
|
|
+ .asComponent();
|
|
|
|
|
+
|
|
|
|
|
+ public FlareCommand() {
|
|
|
|
|
+ super("flare", "Profile your server with Flare", "/flare", Collections.singletonList("profile"));
|
|
|
|
|
+ this.setPermission("airplane.flare");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, String @NotNull [] args) {
|
|
|
|
|
+ if (!testPermission(sender)) return true;
|
|
|
|
|
+ if (PufferfishConfig.accessToken.length() == 0) {
|
|
|
|
|
+ Component clickable = Component.text(BASE_URL, HEX, TextDecoration.UNDERLINED).clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, BASE_URL));
|
|
|
|
|
+
|
|
|
|
|
+ sender.sendMessage(PREFIX.append(Component.text("Flare currently requires an access token to use. To learn more, visit ").color(HEX).append(clickable)));
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!FlareSetup.isSupported()) {
|
|
|
|
|
+ sender.sendMessage(PREFIX.append(
|
|
|
|
|
+ Component.text("Profiling is not supported in this environment, check your startup logs for the error.", NamedTextColor.RED)));
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (ProfilingManager.isProfiling()) {
|
|
|
|
|
+ if (args.length == 1 && args[0].equalsIgnoreCase("status")) {
|
|
|
|
|
+ sender.sendMessage(PREFIX.append(Component.text("Current profile has been ran for " + ProfilingManager.getTimeRan().toString(), HEX)));
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (ProfilingManager.stop()) {
|
|
|
|
|
+ if (!(sender instanceof ConsoleCommandSender)) {
|
|
|
|
|
+ sender.sendMessage(PREFIX.append(Component.text("Profiling has been stopped.", HEX)));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sender.sendMessage(PREFIX.append(Component.text("Profiling has already been stopped.", HEX)));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ProfileType profileType = ProfileType.ITIMER;
|
|
|
|
|
+ if (args.length > 0) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ profileType = ProfileType.valueOf(args[0].toUpperCase());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ sender.sendMessage(PREFIX.append(Component
|
|
|
|
|
+ .text("Invalid profile type ", HEX)
|
|
|
|
|
+ .append(Component.text(args[0], HEX, TextDecoration.BOLD)
|
|
|
|
|
+ .append(Component.text("!", HEX)))
|
|
|
|
|
+ ));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ProfileType finalProfileType = profileType;
|
|
|
|
|
+ Bukkit.getScheduler().runTaskAsynchronously(new MinecraftInternalPlugin(), () -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (ProfilingManager.start(finalProfileType)) {
|
|
|
|
|
+ if (!(sender instanceof ConsoleCommandSender)) {
|
|
|
|
|
+ sender.sendMessage(PREFIX.append(Component
|
|
|
|
|
+ .text("Flare has been started: " + ProfilingManager.getProfilingUri(), HEX)
|
|
|
|
|
+ .clickEvent(ClickEvent.openUrl(ProfilingManager.getProfilingUri()))
|
|
|
|
|
+ ));
|
|
|
|
|
+ sender.sendMessage(PREFIX.append(Component.text(" Run /" + commandLabel + " to stop the Flare.", HEX)));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sender.sendMessage(PREFIX.append(Component
|
|
|
|
|
+ .text("Flare has already been started: " + ProfilingManager.getProfilingUri(), HEX)
|
|
|
|
|
+ .clickEvent(ClickEvent.openUrl(ProfilingManager.getProfilingUri()))
|
|
|
|
|
+ ));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (UserReportableException e) {
|
|
|
|
|
+ sender.sendMessage(Component.text("Flare failed to start: " + e.getUserError(), NamedTextColor.RED));
|
|
|
|
|
+ if (e.getCause() != null) {
|
|
|
|
|
+ MinecraftServer.LOGGER.warn("Flare failed to start", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, String @NotNull [] args) throws IllegalArgumentException {
|
|
|
|
|
+ List<String> list = new ArrayList<>();
|
|
|
|
|
+ if (ProfilingManager.isProfiling()) {
|
|
|
|
|
+ if (args.length == 1) {
|
|
|
|
|
+ String lastWord = args[0];
|
|
|
|
|
+ if (StringUtil.startsWithIgnoreCase("status", lastWord)) {
|
|
|
|
|
+ list.add("status");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (StringUtil.startsWithIgnoreCase("stop", lastWord)) {
|
|
|
|
|
+ list.add("stop");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (args.length <= 1) {
|
|
|
|
|
+ String lastWord = args.length == 0 ? "" : args[0];
|
|
|
|
|
+ for (ProfileType value : ProfileType.values()) {
|
|
|
|
|
+ if (StringUtil.startsWithIgnoreCase(value.getInternalName(), lastWord)) {
|
|
|
|
|
+ list.add(value.name().toLowerCase());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return list;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/flare/FlareSetup.java b/src/main/java/gg/pufferfish/pufferfish/flare/FlareSetup.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..cd22e4dcc8b7b57b10a95ef084637249a98e524f
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/flare/FlareSetup.java
|
|
|
|
|
@@ -0,0 +1,33 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish.flare;
|
|
|
|
|
+
|
|
|
|
|
+import co.technove.flare.FlareInitializer;
|
|
|
|
|
+import co.technove.flare.internal.profiling.InitializationException;
|
|
|
|
|
+import net.minecraft.server.MinecraftServer;
|
|
|
|
|
+import org.apache.logging.log4j.Level;
|
|
|
|
|
+
|
|
|
|
|
+public class FlareSetup {
|
|
|
|
|
+
|
|
|
|
|
+ private static boolean initialized = false;
|
|
|
|
|
+ private static boolean supported = false;
|
|
|
|
|
+
|
|
|
|
|
+ public static void init() {
|
|
|
|
|
+ if (initialized) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ initialized = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ for (String warning : FlareInitializer.initialize()) {
|
|
|
|
|
+ MinecraftServer.LOGGER.warn("Flare warning: " + warning);
|
|
|
|
|
+ }
|
|
|
|
|
+ supported = true;
|
|
|
|
|
+ } catch (InitializationException e) {
|
|
|
|
|
+ MinecraftServer.LOGGER.warn("Failed to enable Flare:", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static boolean isSupported() {
|
|
|
|
|
+ return supported;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/flare/PluginLookup.java b/src/main/java/gg/pufferfish/pufferfish/flare/PluginLookup.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..74aab5eb4b54ffbaf19b8976ffb8ca4a64584006
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/flare/PluginLookup.java
|
|
|
|
|
@@ -0,0 +1,44 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish.flare;
|
|
|
|
|
+
|
|
|
|
|
+import com.google.common.cache.Cache;
|
|
|
|
|
+import com.google.common.cache.CacheBuilder;
|
|
|
|
|
+import org.bukkit.Bukkit;
|
|
|
|
|
+import org.bukkit.plugin.Plugin;
|
|
|
|
|
+import org.bukkit.plugin.java.PluginClassLoader;
|
|
|
|
|
+
|
|
|
|
|
+import java.util.Optional;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
+
|
|
|
|
|
+public class PluginLookup {
|
|
|
|
|
+ private static final Cache<String, String> pluginNameCache = CacheBuilder.newBuilder()
|
|
|
|
|
+ .expireAfterAccess(1, TimeUnit.MINUTES)
|
|
|
|
|
+ .maximumSize(1024)
|
|
|
|
|
+ .build();
|
|
|
|
|
+
|
|
|
|
|
+ public static Optional<String> getPluginForClass(String name) {
|
|
|
|
|
+ if (name.startsWith("net.minecraft") || name.startsWith("java.") || name.startsWith("com.mojang") ||
|
|
|
|
|
+ name.startsWith("com.google") || name.startsWith("it.unimi") || name.startsWith("sun")) {
|
|
|
|
|
+ return Optional.empty();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String existing = pluginNameCache.getIfPresent(name);
|
|
|
|
|
+ if (existing != null) {
|
|
|
|
|
+ return Optional.ofNullable(existing.isEmpty() ? null : existing);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String newValue = "";
|
|
|
|
|
+
|
|
|
|
|
+ for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
|
|
|
|
|
+ ClassLoader classLoader = plugin.getClass().getClassLoader();
|
|
|
|
|
+ if (classLoader instanceof PluginClassLoader) {
|
|
|
|
|
+ if (((PluginClassLoader) classLoader)._airplane_hasClass(name)) {
|
|
|
|
|
+ newValue = plugin.getName();
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pluginNameCache.put(name, newValue);
|
|
|
|
|
+ return Optional.ofNullable(newValue.isEmpty() ? null : newValue);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/flare/ProfilingManager.java b/src/main/java/gg/pufferfish/pufferfish/flare/ProfilingManager.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..e3f76eb11a261c3347f0cd89b5da309bc2dc82f9
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/flare/ProfilingManager.java
|
|
|
|
|
@@ -0,0 +1,151 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish.flare;
|
|
|
|
|
+
|
|
|
|
|
+import co.technove.flare.Flare;
|
|
|
|
|
+import co.technove.flare.FlareAuth;
|
|
|
|
|
+import co.technove.flare.FlareBuilder;
|
|
|
|
|
+import co.technove.flare.exceptions.UserReportableException;
|
|
|
|
|
+import co.technove.flare.internal.profiling.ProfileType;
|
|
|
|
|
+import gg.pufferfish.pufferfish.PufferfishConfig;
|
|
|
|
|
+import gg.pufferfish.pufferfish.PufferfishLogger;
|
|
|
|
|
+import gg.pufferfish.pufferfish.compat.ServerConfigurations;
|
|
|
|
|
+import gg.pufferfish.pufferfish.flare.collectors.GCEventCollector;
|
|
|
|
|
+import gg.pufferfish.pufferfish.flare.collectors.StatCollector;
|
|
|
|
|
+import gg.pufferfish.pufferfish.flare.collectors.TPSCollector;
|
|
|
|
|
+import gg.pufferfish.pufferfish.flare.collectors.WorldCountCollector;
|
|
|
|
|
+import org.bukkit.Bukkit;
|
|
|
|
|
+import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin;
|
|
|
|
|
+import org.bukkit.scheduler.BukkitTask;
|
|
|
|
|
+import oshi.SystemInfo;
|
|
|
|
|
+import oshi.hardware.CentralProcessor;
|
|
|
|
|
+import oshi.hardware.GlobalMemory;
|
|
|
|
|
+import oshi.hardware.HardwareAbstractionLayer;
|
|
|
|
|
+import oshi.hardware.VirtualMemory;
|
|
|
|
|
+import oshi.software.os.OperatingSystem;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.net.URI;
|
|
|
|
|
+import java.time.Duration;
|
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
+import java.util.logging.Level;
|
|
|
|
|
+
|
|
|
|
|
+public class ProfilingManager {
|
|
|
|
|
+
|
|
|
|
|
+ private static Flare currentFlare;
|
|
|
|
|
+ private static BukkitTask currentTask = null;
|
|
|
|
|
+
|
|
|
|
|
+ public static synchronized boolean isProfiling() {
|
|
|
|
|
+ return currentFlare != null && currentFlare.isRunning();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static synchronized String getProfilingUri() {
|
|
|
|
|
+ return Objects.requireNonNull(currentFlare).getURI().map(URI::toString).orElse("Flare is not running");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Duration getTimeRan() {
|
|
|
|
|
+ Flare flare = currentFlare; // copy reference so no need to sync
|
|
|
|
|
+ if (flare == null) {
|
|
|
|
|
+ return Duration.ofMillis(0);
|
|
|
|
|
+ }
|
|
|
|
|
+ return flare.getCurrentDuration();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static synchronized boolean start(ProfileType profileType) throws UserReportableException {
|
|
|
|
|
+ if (currentFlare != null && !currentFlare.isRunning()) {
|
|
|
|
|
+ currentFlare = null; // errored out
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isProfiling()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (Bukkit.isPrimaryThread()) {
|
|
|
|
|
+ throw new UserReportableException("Profiles should be started off-thread");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ OperatingSystem os = new SystemInfo().getOperatingSystem();
|
|
|
|
|
+
|
|
|
|
|
+ SystemInfo systemInfo = new SystemInfo();
|
|
|
|
|
+ HardwareAbstractionLayer hardware = systemInfo.getHardware();
|
|
|
|
|
+
|
|
|
|
|
+ CentralProcessor processor = hardware.getProcessor();
|
|
|
|
|
+ CentralProcessor.ProcessorIdentifier processorIdentifier = processor.getProcessorIdentifier();
|
|
|
|
|
+
|
|
|
|
|
+ GlobalMemory memory = hardware.getMemory();
|
|
|
|
|
+ VirtualMemory virtualMemory = memory.getVirtualMemory();
|
|
|
|
|
+
|
|
|
|
|
+ FlareBuilder builder = new FlareBuilder()
|
|
|
|
|
+ .withProfileType(profileType)
|
|
|
|
|
+ .withMemoryProfiling(true)
|
|
|
|
|
+ .withAuth(FlareAuth.fromTokenAndUrl(PufferfishConfig.accessToken, PufferfishConfig.profileWebUrl))
|
|
|
|
|
+
|
|
|
|
|
+ .withFiles(ServerConfigurations.getCleanCopies())
|
|
|
|
|
+ .withVersion("Primary Version", Bukkit.getVersion())
|
|
|
|
|
+ .withVersion("Bukkit Version", Bukkit.getBukkitVersion())
|
|
|
|
|
+ .withVersion("Minecraft Version", Bukkit.getMinecraftVersion())
|
|
|
|
|
+
|
|
|
|
|
+ .withGraphCategories(CustomCategories.ENTITIES_AND_CHUNKS, CustomCategories.MC_PERF)
|
|
|
|
|
+ .withCollectors(new TPSCollector(), new WorldCountCollector(), new GCEventCollector(), new StatCollector())
|
|
|
|
|
+ .withClassIdentifier(PluginLookup::getPluginForClass)
|
|
|
|
|
+
|
|
|
|
|
+ .withHardware(new FlareBuilder.HardwareBuilder()
|
|
|
|
|
+ .setCoreCount(processor.getPhysicalProcessorCount())
|
|
|
|
|
+ .setThreadCount(processor.getLogicalProcessorCount())
|
|
|
|
|
+ .setCpuModel(processorIdentifier.getName())
|
|
|
|
|
+ .setCpuFrequency(processor.getMaxFreq())
|
|
|
|
|
+
|
|
|
|
|
+ .setTotalMemory(memory.getTotal())
|
|
|
|
|
+ .setTotalSwap(virtualMemory.getSwapTotal())
|
|
|
|
|
+ .setTotalVirtual(virtualMemory.getVirtualMax())
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ .withOperatingSystem(new FlareBuilder.OperatingSystemBuilder()
|
|
|
|
|
+ .setManufacturer(os.getManufacturer())
|
|
|
|
|
+ .setFamily(os.getFamily())
|
|
|
|
|
+ .setVersion(os.getVersionInfo().toString())
|
|
|
|
|
+ .setBitness(os.getBitness())
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ currentFlare = builder.build();
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ PufferfishLogger.LOGGER.log(Level.WARNING, "Failed to read configuration files:", e);
|
|
|
|
|
+ throw new UserReportableException("Failed to load configuration files, check logs for further details.");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ currentFlare.start();
|
|
|
|
|
+ } catch (IllegalStateException e) {
|
|
|
|
|
+ PufferfishLogger.LOGGER.log(Level.WARNING, "Error starting Flare:", e);
|
|
|
|
|
+ throw new UserReportableException("Failed to start Flare, check logs for further details.");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ currentTask = Bukkit.getScheduler().runTaskLater(new MinecraftInternalPlugin(), ProfilingManager::stop, 20 * 60 * 15);
|
|
|
|
|
+ PufferfishLogger.LOGGER.log(Level.INFO, "Flare has been started: " + getProfilingUri());
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static synchronized boolean stop() {
|
|
|
|
|
+ if (!isProfiling()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!currentFlare.isRunning()) {
|
|
|
|
|
+ currentFlare = null;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ PufferfishLogger.LOGGER.log(Level.INFO, "Flare has been stopped: " + getProfilingUri());
|
|
|
|
|
+ try {
|
|
|
|
|
+ currentFlare.stop();
|
|
|
|
|
+ } catch (IllegalStateException e) {
|
|
|
|
|
+ PufferfishLogger.LOGGER.log(Level.WARNING, "Error occurred stopping Flare", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ currentFlare = null;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ currentTask.cancel();
|
|
|
|
|
+ } catch (Throwable t) {
|
|
|
|
|
+ PufferfishLogger.LOGGER.log(Level.WARNING, "Error occurred stopping Flare", t);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ currentTask = null;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/flare/collectors/GCEventCollector.java b/src/main/java/gg/pufferfish/pufferfish/flare/collectors/GCEventCollector.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..d426575c669020f369960107da1e2de2f11f082f
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/flare/collectors/GCEventCollector.java
|
|
|
|
|
@@ -0,0 +1,66 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish.flare.collectors;
|
|
|
|
|
+
|
|
|
|
|
+import co.technove.flare.Flare;
|
|
|
|
|
+import co.technove.flare.internal.FlareInternal;
|
|
|
|
|
+import co.technove.flare.live.CollectorData;
|
|
|
|
|
+import co.technove.flare.live.EventCollector;
|
|
|
|
|
+import co.technove.flare.live.LiveEvent;
|
|
|
|
|
+import co.technove.flare.live.category.GraphCategory;
|
|
|
|
|
+import co.technove.flare.live.formatter.DataFormatter;
|
|
|
|
|
+import com.google.common.collect.ImmutableMap;
|
|
|
|
|
+import com.sun.management.GarbageCollectionNotificationInfo;
|
|
|
|
|
+
|
|
|
|
|
+import javax.management.ListenerNotFoundException;
|
|
|
|
|
+import javax.management.Notification;
|
|
|
|
|
+import javax.management.NotificationEmitter;
|
|
|
|
|
+import javax.management.NotificationListener;
|
|
|
|
|
+import javax.management.openmbean.CompositeData;
|
|
|
|
|
+import java.lang.management.GarbageCollectorMXBean;
|
|
|
|
|
+import java.lang.management.ManagementFactory;
|
|
|
|
|
+
|
|
|
|
|
+public class GCEventCollector extends EventCollector implements NotificationListener {
|
|
|
|
|
+
|
|
|
|
|
+ private static final CollectorData MINOR_GC = new CollectorData("builtin:gc:minor", "Minor GC", "A small pause in the program to allow Garbage Collection to run.", DataFormatter.MILLISECONDS, GraphCategory.SYSTEM);
|
|
|
|
|
+ private static final CollectorData MAJOR_GC = new CollectorData("builtin:gc:major", "Major GC", "A large pause in the program to allow Garbage Collection to run.", DataFormatter.MILLISECONDS, GraphCategory.SYSTEM);
|
|
|
|
|
+ private static final CollectorData UNKNOWN_GC = new CollectorData("builtin:gc:generic", "Major GC", "A run of the Garbage Collection.", DataFormatter.MILLISECONDS, GraphCategory.SYSTEM);
|
|
|
|
|
+
|
|
|
|
|
+ public GCEventCollector() {
|
|
|
|
|
+ super(MINOR_GC, MAJOR_GC, UNKNOWN_GC);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static CollectorData fromString(String string) {
|
|
|
|
|
+ if (string.endsWith("minor GC")) {
|
|
|
|
|
+ return MINOR_GC;
|
|
|
|
|
+ } else if (string.endsWith("major GC")) {
|
|
|
|
|
+ return MAJOR_GC;
|
|
|
|
|
+ }
|
|
|
|
|
+ return UNKNOWN_GC;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void start(Flare flare) {
|
|
|
|
|
+ for (GarbageCollectorMXBean garbageCollectorBean : ManagementFactory.getGarbageCollectorMXBeans()) {
|
|
|
|
|
+ NotificationEmitter notificationEmitter = (NotificationEmitter) garbageCollectorBean;
|
|
|
|
|
+ notificationEmitter.addNotificationListener(this, null, null);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void stop(Flare flare) {
|
|
|
|
|
+ for (GarbageCollectorMXBean garbageCollectorBean : ManagementFactory.getGarbageCollectorMXBeans()) {
|
|
|
|
|
+ NotificationEmitter notificationEmitter = (NotificationEmitter) garbageCollectorBean;
|
|
|
|
|
+ try {
|
|
|
|
|
+ notificationEmitter.removeNotificationListener(this);
|
|
|
|
|
+ } catch (ListenerNotFoundException e) {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void handleNotification(Notification notification, Object o) {
|
|
|
|
|
+ if (notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) {
|
|
|
|
|
+ GarbageCollectionNotificationInfo gcInfo = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData());
|
|
|
|
|
+ reportEvent(new LiveEvent(fromString(gcInfo.getGcAction()), System.currentTimeMillis(), (int) gcInfo.getGcInfo().getDuration(), ImmutableMap.of()));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/flare/collectors/StatCollector.java b/src/main/java/gg/pufferfish/pufferfish/flare/collectors/StatCollector.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..a22c6dbae53667e4c72464fa27153aee30c7946e
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/flare/collectors/StatCollector.java
|
|
|
|
|
@@ -0,0 +1,41 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish.flare.collectors;
|
|
|
|
|
+
|
|
|
|
|
+import co.technove.flare.live.CollectorData;
|
|
|
|
|
+import co.technove.flare.live.LiveCollector;
|
|
|
|
|
+import co.technove.flare.live.category.GraphCategory;
|
|
|
|
|
+import co.technove.flare.live.formatter.DataFormatter;
|
|
|
|
|
+import com.sun.management.OperatingSystemMXBean;
|
|
|
|
|
+import oshi.SystemInfo;
|
|
|
|
|
+import oshi.hardware.CentralProcessor;
|
|
|
|
|
+
|
|
|
|
|
+import java.lang.management.ManagementFactory;
|
|
|
|
|
+import java.time.Duration;
|
|
|
|
|
+
|
|
|
|
|
+public class StatCollector extends LiveCollector {
|
|
|
|
|
+
|
|
|
|
|
+ private static final CollectorData CPU = new CollectorData("builtin:stat:cpu", "CPU Load", "The total amount of CPU usage across all cores.", DataFormatter.PERCENT, GraphCategory.SYSTEM);
|
|
|
|
|
+ private static final CollectorData CPU_PROCESS = new CollectorData("builtin:stat:cpu_process", "Process CPU", "The amount of CPU being used by this process.", DataFormatter.PERCENT, GraphCategory.SYSTEM);
|
|
|
|
|
+ private static final CollectorData MEMORY = new CollectorData("builtin:stat:memory_used", "Memory", "The amount of memory being used currently.", DataFormatter.BYTES, GraphCategory.SYSTEM);
|
|
|
|
|
+ private static final CollectorData MEMORY_TOTAL = new CollectorData("builtin:stat:memory_total", "Memory Total", "The total amount of memory allocated.", DataFormatter.BYTES, GraphCategory.SYSTEM);
|
|
|
|
|
+
|
|
|
|
|
+ private final OperatingSystemMXBean bean;
|
|
|
|
|
+ private final CentralProcessor processor;
|
|
|
|
|
+
|
|
|
|
|
+ public StatCollector() {
|
|
|
|
|
+ super(CPU, CPU_PROCESS, MEMORY, MEMORY_TOTAL);
|
|
|
|
|
+ this.interval = Duration.ofSeconds(5);
|
|
|
|
|
+
|
|
|
|
|
+ this.bean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
|
|
|
|
|
+ this.processor = new SystemInfo().getHardware().getProcessor();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void run() {
|
|
|
|
|
+ Runtime runtime = Runtime.getRuntime();
|
|
|
|
|
+
|
|
|
|
|
+ this.report(CPU, this.processor.getSystemLoadAverage(1)[0] / 100); // percentage
|
|
|
|
|
+ this.report(CPU_PROCESS, this.bean.getProcessCpuLoad());
|
|
|
|
|
+ this.report(MEMORY, runtime.totalMemory() - runtime.freeMemory());
|
|
|
|
|
+ this.report(MEMORY_TOTAL, runtime.totalMemory());
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/flare/collectors/TPSCollector.java b/src/main/java/gg/pufferfish/pufferfish/flare/collectors/TPSCollector.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..40447d00aefb5ffedb8a2ee87155a04088f0649f
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/flare/collectors/TPSCollector.java
|
|
|
|
|
@@ -0,0 +1,31 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish.flare.collectors;
|
|
|
|
|
+
|
|
|
|
|
+import co.technove.flare.live.CollectorData;
|
|
|
|
|
+import co.technove.flare.live.LiveCollector;
|
|
|
|
|
+import co.technove.flare.live.formatter.SuffixFormatter;
|
|
|
|
|
+import gg.pufferfish.pufferfish.flare.CustomCategories;
|
|
|
|
|
+import net.minecraft.server.MinecraftServer;
|
|
|
|
|
+import org.bukkit.Bukkit;
|
|
|
|
|
+
|
|
|
|
|
+import java.time.Duration;
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+
|
|
|
|
|
+public class TPSCollector extends LiveCollector {
|
|
|
|
|
+ private static final CollectorData TPS = new CollectorData("airplane:tps", "TPS", "Ticks per second, or how fast the server updates. For a smooth server this should be a constant 20TPS.", SuffixFormatter.of("TPS"), CustomCategories.MC_PERF);
|
|
|
|
|
+ private static final CollectorData MSPT = new CollectorData("airplane:mspt", "MSPT", "Milliseconds per tick, which can show how well your server is performing. This value should always be under 50mspt.", SuffixFormatter.of("mspt"), CustomCategories.MC_PERF);
|
|
|
|
|
+
|
|
|
|
|
+ public TPSCollector() {
|
|
|
|
|
+ super(TPS, MSPT);
|
|
|
|
|
+
|
|
|
|
|
+ this.interval = Duration.ofSeconds(5);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void run() {
|
|
|
|
|
+ long[] times = MinecraftServer.getServer().tickTimes5s.getTimes();
|
|
|
|
|
+ double mspt = ((double) Arrays.stream(times).sum() / (double) times.length) * 1.0E-6D;
|
|
|
|
|
+
|
|
|
|
|
+ this.report(TPS, Math.min(20D, Math.round(Bukkit.getServer().getTPS()[0] * 100d) / 100d));
|
|
|
|
|
+ this.report(MSPT, (double) Math.round(mspt * 100d) / 100d);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/flare/collectors/WorldCountCollector.java b/src/main/java/gg/pufferfish/pufferfish/flare/collectors/WorldCountCollector.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..029d840e28d67d26d3c0dd6785e25dbf15f9226c
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/gg/pufferfish/pufferfish/flare/collectors/WorldCountCollector.java
|
|
|
|
|
@@ -0,0 +1,45 @@
|
|
|
|
|
+package gg.pufferfish.pufferfish.flare.collectors;
|
|
|
|
|
+
|
|
|
|
|
+import co.technove.flare.live.CollectorData;
|
|
|
|
|
+import co.technove.flare.live.LiveCollector;
|
|
|
|
|
+import co.technove.flare.live.formatter.SuffixFormatter;
|
|
|
|
|
+import gg.pufferfish.pufferfish.flare.CustomCategories;
|
|
|
|
|
+import org.bukkit.Bukkit;
|
|
|
|
|
+import org.bukkit.World;
|
|
|
|
|
+
|
|
|
|
|
+import java.time.Duration;
|
|
|
|
|
+
|
|
|
|
|
+public class WorldCountCollector extends LiveCollector {
|
|
|
|
|
+
|
|
|
|
|
+ private static final CollectorData PLAYER_COUNT = new CollectorData("airplane:world:playercount", "Player Count", "The number of players currently on the server.", new SuffixFormatter(" Player", " Players"), CustomCategories.ENTITIES_AND_CHUNKS);
|
|
|
|
|
+ private static final CollectorData ENTITY_COUNT = new CollectorData("airplane:world:entitycount", "Entity Count", "The number of entities in all worlds", new SuffixFormatter(" Entity", " Entities"), CustomCategories.ENTITIES_AND_CHUNKS);
|
|
|
|
|
+ private static final CollectorData CHUNK_COUNT = new CollectorData("airplane:world:chunkcount", "Chunk Count", "The number of chunks currently loaded.", new SuffixFormatter(" Chunk", " Chunks"), CustomCategories.ENTITIES_AND_CHUNKS);
|
|
|
|
|
+ private static final CollectorData TILE_ENTITY_COUNT = new CollectorData("airplane:world:blockentitycount", "Block Entity Count", "The number of block entities currently loaded.", new SuffixFormatter(" Block Entity", " Block Entities"), CustomCategories.ENTITIES_AND_CHUNKS);
|
|
|
|
|
+
|
|
|
|
|
+ public WorldCountCollector() {
|
|
|
|
|
+ super(PLAYER_COUNT, ENTITY_COUNT, CHUNK_COUNT, TILE_ENTITY_COUNT);
|
|
|
|
|
+
|
|
|
|
|
+ this.interval = Duration.ofSeconds(5);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void run() {
|
|
|
|
|
+ if (true) return; // This doesn't work, and it's not worth fixing at the moment.
|
|
|
|
|
+ int entities = 0;
|
|
|
|
|
+ int chunkCount = 0;
|
|
|
|
|
+ int tileEntityCount = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (!Bukkit.isStopping()) {
|
|
|
|
|
+ for (World world : Bukkit.getWorlds()) {
|
|
|
|
|
+ world.getEntityCount();
|
|
|
|
|
+ chunkCount += world.getChunkCount();
|
|
|
|
|
+ tileEntityCount += world.getTileEntityCount();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.report(PLAYER_COUNT, Bukkit.getOnlinePlayers().size());
|
|
|
|
|
+ this.report(ENTITY_COUNT, entities);
|
|
|
|
|
+ this.report(CHUNK_COUNT, chunkCount);
|
|
|
|
|
+ this.report(TILE_ENTITY_COUNT, tileEntityCount);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..731ef11c7a025ae95ed8a757b530d834733d0621
|
|
|
|
|
@@ -2171,49 +1445,6 @@ index 0000000000000000000000000000000000000000..facd55463d44cb7e3d2ca6892982f549
|
|
|
|
|
+ return backingMap.size();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
|
|
|
index 8d442c5a498ecf288a0cc0c54889c6e2fda849ce..01bdf134fc21220ab7ecca51f2dcd51c0b466bba 100644
|
|
|
|
|
--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
|
|
|
+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
|
|
|
@@ -7,6 +7,7 @@ import net.kyori.adventure.text.Component;
|
|
|
|
|
import net.kyori.adventure.text.format.NamedTextColor;
|
|
|
|
|
import net.minecraft.network.protocol.Packet;
|
|
|
|
|
import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
|
|
|
|
|
+import org.bukkit.Bukkit; // Pufferfish
|
|
|
|
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
|
|
|
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
|
|
|
|
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
|
|
|
|
@@ -16,6 +17,7 @@ import org.spongepowered.configurate.objectmapping.meta.Setting;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Objects;
|
|
|
|
|
+import java.util.logging.Level; // Pufferfish
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
|
|
|
|
|
public class GlobalConfiguration extends ConfigurationPart {
|
|
|
|
|
@@ -51,6 +53,7 @@ public class GlobalConfiguration extends ConfigurationPart {
|
|
|
|
|
|
|
|
|
|
public class Timings extends ConfigurationPart.Post {
|
|
|
|
|
public boolean enabled = true;
|
|
|
|
|
+ public boolean reallyEnabled = false;
|
|
|
|
|
public boolean verbose = true;
|
|
|
|
|
public String url = "https://timings.aikar.co/";
|
|
|
|
|
public boolean serverNamePrivacy = false;
|
|
|
|
|
@@ -64,6 +67,14 @@ public class GlobalConfiguration extends ConfigurationPart {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void postProcess() {
|
|
|
|
|
+ // Pufferfish start
|
|
|
|
|
+ if (enabled && !reallyEnabled) {
|
|
|
|
|
+ Bukkit.getLogger().log(Level.WARNING, "[Pufferfish] To improve performance, timings have been disabled by default");
|
|
|
|
|
+ Bukkit.getLogger().log(Level.WARNING, "[Pufferfish] You can still use timings by using /timings on, but they will not start on server startup unless you set timings.really-enabled to true in paper.yml");
|
|
|
|
|
+ Bukkit.getLogger().log(Level.WARNING, "[Pufferfish] If you would like to disable this message, either set timings.really-enabled to true or timings.enabled to false.");
|
|
|
|
|
+ }
|
|
|
|
|
+ enabled = reallyEnabled;
|
|
|
|
|
+ // Pufferfish end
|
|
|
|
|
MinecraftTimings.processConfig(this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
|
|
|
index 6efb8b10f17c70b05128039376d254e6beda3841..57e8c6673c7cfe447a75f15506e8000062d813fe 100644
|
|
|
|
|
--- a/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
|
|
|
@@ -4235,43 +3466,6 @@ index f7ea77dd82d978ad307f99c743efacfb34478b3d..009ab06182359862b8f543030ec4fe4e
|
|
|
|
|
+ MinecraftServer.getServer().getRecipeManager().addRecipe(new net.minecraft.world.item.crafting.ShapelessRecipe(CraftNamespacedKey.toMinecraft(this.getKey()), this.getGroup(), CraftRecipe.getCategory(this.getCategory()), CraftItemStack.asNMSCopy(this.getResult()), data, true));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
|
|
|
|
|
index d96399e9bf1a58db5a4a22e58abb99e7660e0694..eb19f679ee498e51d02fe9a961cf02699cf75848 100644
|
|
|
|
|
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
|
|
|
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
|
|
|
|
|
@@ -22,7 +22,8 @@ public class MinecraftInternalPlugin extends PluginBase {
|
|
|
|
|
private boolean enabled = true;
|
|
|
|
|
|
|
|
|
|
private final String pluginName;
|
|
|
|
|
- private PluginDescriptionFile pdf;
|
|
|
|
|
+ private org.bukkit.plugin.PluginLogger logger;
|
|
|
|
|
+ private PluginDescriptionFile pdf; // Pufferfish
|
|
|
|
|
|
|
|
|
|
public MinecraftInternalPlugin() {
|
|
|
|
|
this.pluginName = "Minecraft";
|
|
|
|
|
@@ -81,7 +82,12 @@ public class MinecraftInternalPlugin extends PluginBase {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public PluginLogger getLogger() {
|
|
|
|
|
- throw new UnsupportedOperationException("Not supported.");
|
|
|
|
|
+ // Pufferfish start
|
|
|
|
|
+ if (this.logger == null) {
|
|
|
|
|
+ this.logger = new org.bukkit.plugin.PluginLogger(this); // Pufferfish
|
|
|
|
|
+ }
|
|
|
|
|
+ return this.logger;
|
|
|
|
|
+ // Pufferfish end
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@@ -91,7 +97,7 @@ public class MinecraftInternalPlugin extends PluginBase {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Server getServer() {
|
|
|
|
|
- throw new UnsupportedOperationException("Not supported.");
|
|
|
|
|
+ return org.bukkit.Bukkit.getServer(); // Pufferfish - impl
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
|
|
|
|
index 64c50c52c11214740de7903e5592b8b6b2c170b3..d4f62940504e3ef7a70e13b1f4a7726f23b4c637 100644
|
|
|
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
|
|
|
|
@@ -4285,18 +3479,6 @@ index 64c50c52c11214740de7903e5592b8b6b2c170b3..d4f62940504e3ef7a70e13b1f4a7726f
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
|
|
|
|
|
index e948ec5a573b22645664eb53bc3e9932246544e4..e3845dc3357bbb74885ae3a1a08525adde581235 100644
|
|
|
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
|
|
|
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
|
|
|
|
|
@@ -11,6 +11,7 @@ public class ServerShutdownThread extends Thread {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
+ try { gg.pufferfish.pufferfish.flare.ProfilingManager.stop(); } catch (Throwable t) {} // Pufferfish - shut down Flare if it's running
|
|
|
|
|
try {
|
|
|
|
|
// Paper start - try to shutdown on main
|
|
|
|
|
server.safeShutdown(false, false);
|
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
|
|
|
|
|
index 774556a62eb240da42e84db4502e2ed43495be17..80553face9c70c2a3d897681e7761df85b22d464 100644
|
|
|
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
|
|
|
|
|
|