diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/DebugOverlay.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/DebugOverlay.java index 40acfd5ee2b..1e07784deec 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/DebugOverlay.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/DebugOverlay.java @@ -15,6 +15,7 @@ import org.terasology.engine.persistence.StorageManager; import org.terasology.engine.registry.In; import org.terasology.engine.rendering.nui.CoreScreenLayer; +import org.terasology.engine.utilities.OperatingSystemMemory; import org.terasology.engine.world.WorldProvider; import org.terasology.engine.world.chunks.Chunks; import org.terasology.nui.databinding.ReadOnlyBinding; @@ -31,7 +32,7 @@ */ public class DebugOverlay extends CoreScreenLayer { - public static final double MB_SIZE = 1048576.0; + public static final float MB_SIZE = 1048576.0f; @In private Config config; @@ -72,13 +73,40 @@ public Boolean get() { }); UILabel debugLine1 = find("debugLine1", UILabel.class); + + // This limit doesn't change after start-up. + final long dataLimit = OperatingSystemMemory.isAvailable() + ? OperatingSystemMemory.dataAndStackSizeLimit() : -1; + if (debugLine1 != null) { - debugLine1.bindText(new ReadOnlyBinding() { + debugLine1.bindText(new ReadOnlyBinding<>() { @Override public String get() { - double memoryUsage = ((double) Runtime.getRuntime().totalMemory() - (double) Runtime.getRuntime().freeMemory()) / MB_SIZE; - return String.format("FPS: %.2f, Memory Usage: %.2f MB, Total Memory: %.2f MB, Max Memory: %.2f MB", - time.getFps(), memoryUsage, Runtime.getRuntime().totalMemory() / MB_SIZE, Runtime.getRuntime().maxMemory() / MB_SIZE); + Runtime runtime = Runtime.getRuntime(); + long totalHeapSize = runtime.totalMemory(); + float usedHeapMemory = ((float) totalHeapSize - (float) runtime.freeMemory()) / MB_SIZE; + String s = String.format( + "FPS: %.1f, Heap Usage: %.1f MB, Total Heap: %.1f MB, Max Heap: %.1f MB", + time.getFps(), + usedHeapMemory, + totalHeapSize / MB_SIZE, + runtime.maxMemory() / MB_SIZE + ); + if (OperatingSystemMemory.isAvailable()) { + // Check data size, because that's the one comparable to Terasology#setMemoryLimit + long dataSize = OperatingSystemMemory.dataAndStackSize(); + // How much bigger is that than the number reported by the Java runtime? + long nonJavaHeapDataSize = dataSize - totalHeapSize; + String limitString = (dataLimit > 0) + ? String.format(" / %.1f MB (%02d%%)", dataLimit / MB_SIZE, 100 * dataSize / dataLimit) + : ""; + return String.format( + "%s, Data: %.1f MB%s, Extra: %.1f MB", + s, dataSize / MB_SIZE, limitString, nonJavaHeapDataSize / MB_SIZE + ); + } else { + return s; + } } }); } @@ -158,7 +186,7 @@ public String get() { debugInfo.bindText(new ReadOnlyBinding() { @Override public String get() { - return String.format("[H] : Debug Documentation"); + return "[H] : Debug Documentation"; } }); } diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/MetricsMode.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/MetricsMode.java index 9925418814d..0d1c570f5e0 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/MetricsMode.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/MetricsMode.java @@ -45,7 +45,9 @@ public MetricsMode(String name) { */ public abstract boolean isAvailable(); - public abstract boolean isPerformanceManagerMode(); + public boolean isPerformanceManagerMode() { + return false; + } /** * A (human readable) name for the metrics mode. diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/NetworkStatsMode.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/NetworkStatsMode.java index e07f2fec037..13fcc006f36 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/NetworkStatsMode.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/NetworkStatsMode.java @@ -50,9 +50,4 @@ public String getMetrics() { public boolean isAvailable() { return networkSystem.getMode() != NetworkMode.NONE; } - - @Override - public boolean isPerformanceManagerMode() { - return false; - } } diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/NullMetricsMode.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/NullMetricsMode.java index 8ccca7c16eb..f7d0a3fe7c5 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/NullMetricsMode.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/NullMetricsMode.java @@ -17,9 +17,4 @@ public String getMetrics() { public boolean isAvailable() { return true; } - - @Override - public boolean isPerformanceManagerMode() { - return false; - } } diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/RunningThreadsMode.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/RunningThreadsMode.java index 3b5bf6ae252..58d78a54a98 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/RunningThreadsMode.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/RunningThreadsMode.java @@ -30,9 +30,4 @@ public String getMetrics() { public boolean isAvailable() { return true; } - - @Override - public boolean isPerformanceManagerMode() { - return false; - } } diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/WorldRendererMode.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/WorldRendererMode.java index 003777ddf72..fd2d6fe7953 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/WorldRendererMode.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/metrics/WorldRendererMode.java @@ -20,9 +20,4 @@ public String getMetrics() { public boolean isAvailable() { return CoreRegistry.get(WorldRenderer.class) != null; } - - @Override - public boolean isPerformanceManagerMode() { - return false; - } } diff --git a/engine/src/main/java/org/terasology/engine/utilities/OperatingSystemMemory.java b/engine/src/main/java/org/terasology/engine/utilities/OperatingSystemMemory.java new file mode 100644 index 00000000000..c1539c6679f --- /dev/null +++ b/engine/src/main/java/org/terasology/engine/utilities/OperatingSystemMemory.java @@ -0,0 +1,92 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.engine.utilities; + +import com.sun.jna.platform.unix.LibC; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Monitor process memory usage. + *

+ * This checks process's total memory usage as seen by the operating system. + * This includes memory not managed by the JVM. + */ +public final class OperatingSystemMemory { + public static final int PAGE_SIZE = 1 << 12; // 4 kB on x86 platforms + + private static final Path PROC_STATM = Path.of("/proc/self/statm"); + + private OperatingSystemMemory() { } + + public static boolean isAvailable() { + return OS.IS_LINUX; + } + + public static long residentSetSize() { + try { + return STATM.RESIDENT.inBytes(Files.readString(PROC_STATM)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static long dataAndStackSize() { + try { + return STATM.DATA.inBytes(Files.readString(PROC_STATM)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static long dataAndStackSizeLimit() { + final LibC.Rlimit dataLimit = new LibC.Rlimit(); + LibC.INSTANCE.getrlimit(LibC.RLIMIT_DATA, dataLimit); + return dataLimit.rlim_cur; + } + + /** + * The fields of /proc/[pid]/statm + *

+ * Note from proc(5): + *

+ * Some of these values are inaccurate because of a kernel-internal scalability optimization. + * If accurate values are required, use /proc/[pid]/smaps or /proc/[pid]/smaps_rollup instead, + * which are much slower but provide accurate, detailed information. + *

+ */ + enum STATM { + /** total program size */ + SIZE(0), + /** resident set size */ + RESIDENT(1), + /** number of resident shared pages */ + SHARED(2), + /** text (code) */ + TEXT(3), + /** unused since Linux 2.6 */ + LIB(4), + /** data + stack */ + DATA(5), + /** unused since Linux 2.6 */ + DT(6); + + private final short index; + + STATM(int i) { + index = (short) i; + } + + public long rawValue(String line) { + return Long.parseLong(line.split(" ")[index]); + } + + public long inBytes(String line) { + return rawValue(line) * PAGE_SIZE; + } + } +}