From 35a3e2e02f86161301da17df76e0cc7042c2ac6b Mon Sep 17 00:00:00 2001 From: PrexorJustin Date: Sun, 5 Jan 2025 03:59:23 +0100 Subject: [PATCH 1/2] feat: Windows Support --- .../generators/DefaultEnvironmentConfigGenerator.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/config/environment/generators/DefaultEnvironmentConfigGenerator.kt b/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/config/environment/generators/DefaultEnvironmentConfigGenerator.kt index 3febe62..ef0c494 100644 --- a/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/config/environment/generators/DefaultEnvironmentConfigGenerator.kt +++ b/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/config/environment/generators/DefaultEnvironmentConfigGenerator.kt @@ -4,6 +4,7 @@ import app.simplecloud.droplet.serverhost.runtime.config.environment.Environment import app.simplecloud.droplet.serverhost.runtime.config.environment.EnvironmentConfigGenerator import app.simplecloud.droplet.serverhost.runtime.config.environment.EnvironmentStartConfig import app.simplecloud.droplet.serverhost.runtime.launcher.ServerHostStartCommand +import java.io.File import java.nio.file.Paths import kotlin.io.path.absolutePathString @@ -20,7 +21,7 @@ object DefaultEnvironmentConfigGenerator : EnvironmentConfigGenerator { "-Xmx%MAX_MEMORY%M", "-Dcom.mojang.eula.agree=true", "-cp", - "${libs}/*:%SERVER_FILE%", + "${libs}/*${File.pathSeparator}%SERVER_FILE%", "%MAIN_CLASS%", "nogui" ) From 1d9cb135d7e25e4870c7dfed06b5f320f42c29a9 Mon Sep 17 00:00:00 2001 From: PrexorJustin Date: Sun, 5 Jan 2025 06:47:54 +0100 Subject: [PATCH 2/2] feat: fixed ram and cpu calculation on windows --- .../runtime/process/ProcessFinder.kt | 39 +++++++-- .../runtime/process/WindowsProcessInfo.kt | 86 ++++++++++++++++++- 2 files changed, 114 insertions(+), 11 deletions(-) diff --git a/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/process/ProcessFinder.kt b/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/process/ProcessFinder.kt index f1ae99a..79430eb 100644 --- a/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/process/ProcessFinder.kt +++ b/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/process/ProcessFinder.kt @@ -3,6 +3,7 @@ package app.simplecloud.droplet.serverhost.runtime.process import app.simplecloud.droplet.serverhost.runtime.util.ProcessDirectory import java.nio.file.Path import java.util.* +import java.util.concurrent.atomic.AtomicReference import kotlin.io.path.absolutePathString object ProcessFinder { @@ -23,19 +24,41 @@ object ProcessFinder { } fun findHighestExecutableProcess(handle: ProcessHandle, executable: String): Optional { - var returned = Optional.empty() + // Check the current handle first + val currentCommand = handle.info().command().orElse("").lowercase() + if (currentCommand.contains(executable.lowercase())) { + return Optional.of(handle) + } + + val found = AtomicReference>(Optional.empty()) + + // Check immediate children handle.children().forEach { child -> - if (!returned.isEmpty) return@forEach - if (ProcessInfo.of(child).getCommand().lowercase().startsWith(executable)) { - returned = Optional.of(child) + if (found.get().isPresent) return@forEach + + val command = child.info().command().orElse("").lowercase() + + if (command.contains(executable.lowercase())) { + found.set(Optional.of(child)) } } - if (!returned.isEmpty) return returned + + // Return if we found a match in the first pass + if (found.get().isPresent) { + return found.get() + } + + // Recursive search through children handle.children().forEach { child -> - if (!returned.isEmpty) return@forEach - returned = findHighestExecutableProcess(child, executable) + if (found.get().isPresent) return@forEach + + val childResult = findHighestExecutableProcess(child, executable) + if (childResult.isPresent) { + found.set(childResult) + } } - return returned + + return found.get() } } \ No newline at end of file diff --git a/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/process/WindowsProcessInfo.kt b/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/process/WindowsProcessInfo.kt index 283af63..e9ce00b 100644 --- a/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/process/WindowsProcessInfo.kt +++ b/serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/process/WindowsProcessInfo.kt @@ -1,16 +1,96 @@ package app.simplecloud.droplet.serverhost.runtime.process class WindowsProcessInfo(private val pid: Long) : ProcessInfo { + override fun getCommand(): String { - return ProcessHandle.of(pid).get().info().commandLine().get() + return ProcessHandle.of(pid) + .map { it.info().command().orElse("") } + .orElse("") } override fun getRamAndCpuPercent(): Pair { - TODO("Not yet implemented") + try { + val memCmd = Runtime.getRuntime().exec( + arrayOf( + "cmd.exe", + "/c", + "tasklist /FI \"PID eq $pid\" /FO CSV /NH /V" + ) + ) + + var memKB: Long + memCmd.waitFor() + memCmd.inputReader(Charsets.UTF_8).use { reader -> + val output = reader.readText() + memKB = output.split(",") + .getOrNull(4) + ?.trim() + ?.removeSurrounding("\"") + ?.replace(Regex("[^0-9]"), "") // Remove any non-numeric characters + ?.toLongOrNull() ?: 0L + + } + + val cpuCmd = Runtime.getRuntime().exec( + arrayOf( + "cmd.exe", + "/c", + "tasklist /FI \"PID eq $pid\" /FO CSV /NH /V" + ) + ) + + var cpuPercent = 0.0 + cpuCmd.waitFor() + cpuCmd.inputReader(Charsets.UTF_8).use { reader -> + val output = reader.readText() + val cpuTimeStr = output.split(",") + .getOrNull(8) + ?.trim() + ?.removeSurrounding("\"") + ?: "00:00:00" + + // Convert HH:MM:SS to seconds + val parts = cpuTimeStr.split(":") + val totalSeconds = if (parts.size == 3) { + (parts[0].toLongOrNull() ?: 0L) * 3600L + + (parts[1].toLongOrNull() ?: 0L) * 60L + + (parts[2].toLongOrNull() ?: 0L) + } else { + 0L + } + + val uptime = ProcessHandle.of(pid) + .map { handle -> + handle.info().startInstant() + .map { start -> + java.time.Duration.between(start, java.time.Instant.now()).seconds + } + .orElse(1L) + } + .orElse(1L) + .coerceAtLeast(1L) + + val processors = Runtime.getRuntime().availableProcessors() + cpuPercent = (totalSeconds.toDouble() / uptime.toDouble() * 100.0 * processors) + .coerceIn(0.0, 100.0) + } + + val totalMemBytes = when (val osBean = java.lang.management.ManagementFactory.getOperatingSystemMXBean()) { + is com.sun.management.OperatingSystemMXBean -> osBean.totalPhysicalMemorySize + else -> Runtime.getRuntime().maxMemory() + } + + val memBytes = memKB * 1024L + val memPercent = (memBytes.toDouble() / totalMemBytes.toDouble() * 100.0) + .coerceIn(0.0, 100.0) + + return Pair(memPercent, cpuPercent) + } catch (e: Exception) { + return Pair(0.0, 0.0) + } } override fun asHandle(): ProcessHandle { return ProcessHandle.of(pid).get() } - } \ No newline at end of file