From 48b757f6931dfbaea85e960f49ab7f6e2fffd9c5 Mon Sep 17 00:00:00 2001 From: RePixelatedMC Date: Wed, 15 Nov 2023 08:43:38 +0100 Subject: [PATCH] Forgot those --- core/src/main/java/com/volmit/iris/Iris.java | 3 - .../iris/core/commands/CommandDeveloper.java | 67 ++++++++++ .../iris/core/commands/CommandIris.java | 1 + .../iris/core/service/HotDropWorldSVC.java | 18 ++- .../iris/core/service/MemoryLeakSVC.java | 78 ++++++++++++ .../iris/engine/mantle/EngineMantle.java | 7 ++ .../com/volmit/iris/util/mantle/Mantle.java | 115 +++++++++++++----- .../volmit/iris/util/misc/getHardware.java | 14 +++ 8 files changed, 267 insertions(+), 36 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java create mode 100644 core/src/main/java/com/volmit/iris/core/service/MemoryLeakSVC.java diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index bffe76848..4b48913fe 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -31,7 +31,6 @@ import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.v1X.NMSBinding1X; import com.volmit.iris.core.pregenerator.LazyPregenerator; import com.volmit.iris.core.safeguard.ServerBootSFG; -import com.volmit.iris.core.service.HotDropWorldSVC; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.EnginePanic; @@ -474,8 +473,6 @@ public class Iris extends VolmitPlugin implements Listener { UtilsSFG.SupportedServerSoftware(); UtilsSFG.printIncompatibleWarnings(); UtilsSFG.unstablePrompt(); - HotDropWorldSVC hotDropWorldSVC = new HotDropWorldSVC(this); - hotDropWorldSVC.start(); autoStartStudio(); ServerBootSFG.CheckIrisWorlds(); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java new file mode 100644 index 000000000..0cc026dff --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -0,0 +1,67 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * 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 com.volmit.iris.core.commands; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.mantle.EngineMantle; +import com.volmit.iris.util.decree.DecreeExecutor; +import com.volmit.iris.util.decree.DecreeOrigin; +import com.volmit.iris.util.decree.annotations.Decree; +import com.volmit.iris.util.decree.annotations.Param; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.mantle.Mantle; +import org.bukkit.Bukkit; +import org.bukkit.World; + +@Decree(name = "Developer", origin = DecreeOrigin.BOTH, description = "Iris World Manager", aliases = {"dev"}) +public class CommandDeveloper implements DecreeExecutor { + + @Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true) + public void EngineStatus( + @Param(description = "World") + World world + ) { + if (!IrisToolbelt.isIrisWorld(world)) { + sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", Bukkit.getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); + return; + } + + Engine engine = IrisToolbelt.access(world).getEngine(); + if(engine != null) { + long lastUseSize = engine.getMantle().getLastUseMapMemoryUsage(); + long outputToUnload = engine.getMantle().getToUnload(); + + Iris.info("-------------------------"); + Iris.info(C.DARK_PURPLE + "Engine Status"); + Iris.info(C.DARK_PURPLE + "Tectonic Plates: " + C.LIGHT_PURPLE + engine.getMantle().getLoadedRegionCount()); + Iris.info(C.DARK_PURPLE + "Tectonic ToUnload: " + C.LIGHT_PURPLE + outputToUnload); + Iris.info(C.DARK_PURPLE + "Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize())); + Iris.info(C.DARK_PURPLE + "LastUse Size: " + C.LIGHT_PURPLE + Form.mem(lastUseSize) + " MB"); + Iris.info("-------------------------"); + } else { + Iris.info(C.RED + "Engine is null!"); + } + } +} + + diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java index 7d13a044b..b80548c29 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java @@ -69,6 +69,7 @@ public class CommandIris implements DecreeExecutor { private CommandEdit edit; private CommandFind find; private CommandWorldManager manager; + private CommandDeveloper developer; public static @Getter String BenchDimension; diff --git a/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java b/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java index 44166f64b..88bab482b 100644 --- a/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java @@ -11,16 +11,28 @@ import com.google.gson.JsonParseException; import com.volmit.iris.Iris; import com.volmit.iris.util.SFG.WorldHandlerSFG; import com.volmit.iris.util.format.C; +import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.scheduling.Looper; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; -public class HotDropWorldSVC extends Looper { +public class HotDropWorldSVC extends Looper implements IrisService { private WatchService watchService; private JavaPlugin plugin; - public HotDropWorldSVC(JavaPlugin plugin) { - this.plugin = plugin; + @Override + public void onEnable() { + this.plugin = Iris.instance; // Assuming Iris.instance is your plugin instance + initializeWatchService(); + + } + + @Override + public void onDisable() { + + } + + private void initializeWatchService() { try { this.watchService = FileSystems.getDefault().newWatchService(); Path path = Paths.get(Bukkit.getWorldContainer().getAbsolutePath()); diff --git a/core/src/main/java/com/volmit/iris/core/service/MemoryLeakSVC.java b/core/src/main/java/com/volmit/iris/core/service/MemoryLeakSVC.java new file mode 100644 index 000000000..3baab50ed --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/service/MemoryLeakSVC.java @@ -0,0 +1,78 @@ +package com.volmit.iris.core.service; + +import java.nio.file.*; +import static java.nio.file.StandardWatchEventKinds.*; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonParseException; +import com.volmit.iris.Iris; +import com.volmit.iris.core.service.StudioSVC; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.util.SFG.WorldHandlerSFG; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.misc.getHardware; +import com.volmit.iris.util.plugin.IrisService; +import com.volmit.iris.util.scheduling.Looper; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.plugin.java.JavaPlugin; + +public class MemoryLeakSVC extends Looper implements IrisService { + private WatchService watchService; + private JavaPlugin plugin; + + @Override + public void onEnable() { + //Iris.info("Enabled Mem Leak Detection thing wow it actually worked"); + //this.plugin = Iris.instance; + } + + @Override + public void onDisable() { + + } + + @Override + protected long loop() { + try { + if (getHardware.getAvailableProcessMemory() < 50){ + PrintMemoryLeakDetected(); + for (World world : Bukkit.getWorlds()) { + if (IrisToolbelt.isIrisWorld(world)){ + Engine engine = IrisToolbelt.access(world).getEngine(); + if (engine.getMantle().getLoadedRegionCount() > 0){ + if (!IrisToolbelt.isIrisWorld(world)) { + Iris.info(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", Bukkit.getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); + return -1; + } + Iris.info(C.GREEN + "Unloading world: " + world.getName()); + try { + IrisToolbelt.evacuate(world); + Bukkit.unloadWorld(world, false); + Iris.info(C.GREEN + "World unloaded successfully."); + } catch (Exception e) { + Iris.info(C.RED + "Failed to unload the world: " + e.getMessage()); + e.printStackTrace(); + } + + } + } + } + } + } catch (Throwable e) { + Iris.reportError(e); + e.printStackTrace(); + return -1; + } + + return 1000; + } + public void PrintMemoryLeakDetected(){ + Iris.info(C.DARK_RED + "--==< MEMORY LEAK DETECTED >==--"); + } +} + diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index 5dbd7a7d1..04a5263f3 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -257,6 +257,9 @@ public interface EngineMantle extends IObjectPlacer { default int getLoadedRegionCount() { return getMantle().getLoadedRegionCount(); } + default long getLastUseMapMemoryUsage(){ + return getMantle().LastUseMapMemoryUsage(); + } MantleJigsawComponent getJigsawComponent(); @@ -288,4 +291,8 @@ public interface EngineMantle extends IObjectPlacer { }); } } + + default long getToUnload(){ + return getMantle().ToUnloadTectonic(); + } } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index 113405abf..6630fc019 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -36,6 +36,7 @@ import com.volmit.iris.util.function.Consumer4; import com.volmit.iris.util.math.M; import com.volmit.iris.util.matter.Matter; import com.volmit.iris.util.matter.MatterSlice; +import com.volmit.iris.util.misc.getHardware; import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.HyperLock; import com.volmit.iris.util.parallel.MultiBurst; @@ -45,16 +46,18 @@ import org.bukkit.Chunk; import java.io.EOFException; import java.io.File; import java.io.IOException; +import java.util.HashSet; import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; +import java.util.Set; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; /** * The mantle can store any type of data slice anywhere and manage regions & IO on it's own. * This class is fully thread safe read & writeNodeData */ + public class Mantle { private final File dataFolder; private final int worldHeight; @@ -66,6 +69,11 @@ public class Mantle { private final AtomicBoolean closed; private final MultiBurst ioBurst; private final AtomicBoolean io; + private final Object gcMonitor = new Object(); + long apm = getHardware.getAvailableProcessMemory(); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + int tectonicLimitBeforeOutMemory; + double tectonicLimit = 30; /** * Create a new mantle @@ -374,45 +382,92 @@ public class Mantle { Iris.debug("The Mantle has Closed " + C.DARK_AQUA + dataFolder.getAbsolutePath()); } + /** + * Estimates the memory usage of the lastUse map. + * + * @return Estimated memory usage in bytes. + */ + + public long LastUseMapMemoryUsage() { + long numberOfEntries = lastUse.size(); + long bytesPerEntry = Long.BYTES * 2; + return numberOfEntries * bytesPerEntry; + } + /** * Save & unload regions that have not been used for more than the * specified amount of milliseconds * - * @param idleDuration the duration + * @param baseIdleDuration the duration */ - public synchronized void trim(long idleDuration) { + + AtomicInteger FakeToUnload = new AtomicInteger(0); + + + public synchronized void trim(long baseIdleDuration) { if (closed.get()) { throw new RuntimeException("The Mantle is closed"); } + double adjustedIdleDuration = baseIdleDuration; + + if (loadedRegions.size() > tectonicLimit) { + adjustedIdleDuration = Math.max(adjustedIdleDuration - ( 1000 * (loadedRegions.size() - tectonicLimit) * 1.35), 4000); + } + io.set(true); - Iris.debug("Trimming Tectonic Plates older than " + Form.duration((double) idleDuration, 0)); - unload.clear(); - for (Long i : lastUse.keySet()) { - hyperLock.withLong(i, () -> { - if (M.ms() - lastUse.get(i) >= idleDuration) { - unload.add(i); - } - }); + try { + Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration, 0)); + Set toUnload = new HashSet<>(); + + for (Long i : lastUse.keySet()) { + double finalAdjustedIdleDuration = adjustedIdleDuration; + hyperLock.withLong(i, () -> { + if (M.ms() - lastUse.get(i) >= finalAdjustedIdleDuration) { + toUnload.add(i); + FakeToUnload.addAndGet(1); + Iris.debug("Tectonic Region added to unload"); + } + }); + } + + // Create a thread pool to handle unloading tasks + ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + + for (Long i : toUnload) { + executor.submit(() -> { + hyperLock.withLong(i, () -> { + TectonicPlate m = loadedRegions.get(i); + if (m != null) { + try { + m.write(fileForRegion(dataFolder, i)); + loadedRegions.remove(i); + lastUse.remove(i); + Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(i) + " " + Cache.keyZ(i)); + FakeToUnload.addAndGet(-1); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + }); + } + + // Shutdown the executor and wait for tasks to complete + executor.shutdown(); + executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } finally { + io.set(false); } - - for (Long i : unload) { - hyperLock.withLong(i, () -> { - TectonicPlate m = loadedRegions.remove(i); - lastUse.remove(i); - - try { - m.write(fileForRegion(dataFolder, i)); - } catch (IOException e) { - e.printStackTrace(); - } - - Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(i) + " " + Cache.keyZ(i)); - }); - } - io.set(false); } + public long ToUnloadTectonic(){ + return FakeToUnload.get(); + } + /** * This retreives a future of the Tectonic Plate at the given coordinates. @@ -443,7 +498,7 @@ public class Mantle { try { return getSafe(x, z).get(); } catch (InterruptedException e) { - Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a thread interruption (hotload?)"); + Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a thread intterruption (hotload?)"); Iris.reportError(e); } catch (ExecutionException e) { Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a thread execution exception (engine close?)"); diff --git a/core/src/main/java/com/volmit/iris/util/misc/getHardware.java b/core/src/main/java/com/volmit/iris/util/misc/getHardware.java index ea5aa06d5..80221590d 100644 --- a/core/src/main/java/com/volmit/iris/util/misc/getHardware.java +++ b/core/src/main/java/com/volmit/iris/util/misc/getHardware.java @@ -22,6 +22,20 @@ public class getHardware { long maxMemory = Runtime.getRuntime().maxMemory() / (1024 * 1024); return maxMemory; } + public static long getProcessUsedMemory() { + Runtime runtime = Runtime.getRuntime(); + + long totalMemory = runtime.totalMemory(); + long freeMemory = runtime.freeMemory(); + long usedMemory = totalMemory - freeMemory; + + return usedMemory / (1024 * 1024); + } + + public static long getAvailableProcessMemory(){ + long availableMemory = getHardware.getProcessMemory() - getHardware.getProcessUsedMemory(); + return availableMemory; + } public static String getCPUModel() { try {