diff --git a/src/main/kotlin/org/server_info/server_info/Server_info.kt b/src/main/kotlin/org/server_info/server_info/Server_info.kt index d7f50f7..97c5b7f 100644 --- a/src/main/kotlin/org/server_info/server_info/Server_info.kt +++ b/src/main/kotlin/org/server_info/server_info/Server_info.kt @@ -1,7 +1,6 @@ package org.server_info.server_info import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor import org.bukkit.Bukkit import org.bukkit.command.Command import org.bukkit.command.CommandExecutor @@ -12,6 +11,7 @@ import org.bukkit.plugin.java.JavaPlugin import org.bukkit.scheduler.BukkitRunnable import java.lang.management.ManagementFactory import java.text.DecimalFormat +import com.sun.management.OperatingSystemMXBean class Server_info : JavaPlugin() { @@ -21,25 +21,23 @@ class Server_info : JavaPlugin() { private var tps = 20.0 private val df = DecimalFormat("#.##") - // Server title that appears at the top private var serverTitle = "Paper" + private var cpuUsage = 0.0 + private var lastCpuTime = 0L + private var lastSampleTime = 0L override fun onEnable() { - // Save default config if it doesn't exist saveDefaultConfig() - - // Load configuration loadConfiguration() - // Register command getCommand("serverinfo")?.let { command -> val commandExecutor = ServerInfoCommand(this) command.setExecutor(commandExecutor) command.tabCompleter = commandExecutor } - // Start server info collectors startTPSCalculation() + startCPUMonitor() startTabListUpdater() logger.info("ServerInfo plugin has been enabled!") @@ -47,15 +45,11 @@ class Server_info : JavaPlugin() { override fun onDisable() { logger.info("ServerInfo plugin has been disabled!") - // Save configuration saveConfig() } private fun loadConfiguration() { - // Load server title from config, default to "Paper" if not set serverTitle = config.getString("server-title") ?: "Paper" - - // Create config options if they don't exist if (!config.contains("server-title")) { config.set("server-title", serverTitle) saveConfig() @@ -66,12 +60,10 @@ class Server_info : JavaPlugin() { serverTitle = title config.set("server-title", title) saveConfig() - // Update tablist immediately to reflect the change updateTabList() } private fun startTPSCalculation() { - // Better TPS calculation method with moving average object : BukkitRunnable() { override fun run() { tickCount++ @@ -80,28 +72,79 @@ class Server_info : JavaPlugin() { val elapsed = now - lastTickTime lastTickTime = now - // Store data point - time per tick in ms - // Only add if we've gone through at least one tick to avoid startup anomalies if (tickCount > 1) { tickTimeList.add(elapsed) } - // Keep only the last 600 data points (30 seconds at 20 TPS) while (tickTimeList.size > 600) { tickTimeList.removeAt(0) } - // Calculate average tick time and TPS if we have data if (tickTimeList.isNotEmpty()) { val avgTickTime = tickTimeList.average() tps = 1000.0 / avgTickTime - - // Clamp TPS between 0 and 20 - if (tps > 20.0) tps = 20.0 - if (tps < 0.0) tps = 0.0 + tps = tps.coerceIn(0.0, 20.0) } } - }.runTaskTimer(this, 1L, 1L) // Run every tick + }.runTaskTimer(this, 1L, 1L) + } + + private fun startCPUMonitor() { + object : BukkitRunnable() { + override fun run() { + updateCPUUsage() + } + }.runTaskTimer(this, 20L, 20L) // Update every second + } + + private fun updateCPUUsage() { + try { + val osBean = ManagementFactory.getOperatingSystemMXBean() + + if (osBean is com.sun.management.OperatingSystemMXBean) { + // Modern JVM with com.sun.management API + cpuUsage = osBean.processCpuLoad * 100 + } else { + // Fallback to more basic metrics + val runtime = Runtime.getRuntime() + val processors = runtime.availableProcessors() + + // Get JVM CPU time if available + val bean = ManagementFactory.getThreadMXBean() + if (bean.isThreadCpuTimeSupported && bean.isThreadCpuTimeEnabled) { + var totalCpuTime = 0L + for (threadId in bean.allThreadIds) { + val cpuTime = bean.getThreadCpuTime(threadId) + if (cpuTime >= 0) totalCpuTime += cpuTime + } + + val now = System.currentTimeMillis() + + if (lastCpuTime > 0L && lastSampleTime > 0L) { + val elapsedCpu = totalCpuTime - lastCpuTime + val elapsedTime = (now - lastSampleTime) * 1000000 // Convert to nanoseconds + + if (elapsedTime > 0) { + // Calculate CPU usage as a percentage across all cores + cpuUsage = (elapsedCpu * 100.0) / (elapsedTime * processors) + cpuUsage = cpuUsage.coerceIn(0.0, 100.0) + } + } + + lastCpuTime = totalCpuTime + lastSampleTime = now + } else { + // If CPU time not supported, use a basic estimate ( If this happens please check the server for issues! ) + val threadCount = ManagementFactory.getThreadMXBean().threadCount + logger.warning(/* msg = */ "§4Accuarte CPU Monitoring isnt supported! Check Server for Issues!") + cpuUsage = (threadCount * 100.0) / (processors * 20.0) + cpuUsage = cpuUsage.coerceIn(0.0, 100.0) + } + } + } catch (e: Exception) { + logger.warning("Failed to calculate CPU usage: ${e.message}") + cpuUsage = 0.0 + } } private fun startTabListUpdater() { @@ -109,7 +152,7 @@ class Server_info : JavaPlugin() { override fun run() { updateTabList() } - }.runTaskTimer(this, 20L, 20L) // Update every second + }.runTaskTimer(this, 20L, 20L) } private fun updateTabList() { @@ -122,76 +165,73 @@ class Server_info : JavaPlugin() { } private fun getTabListHeader(): Component { - return Component.text("§6§l$serverTitle\n§r§7Online players: §f${server.onlinePlayers.size}/${server.maxPlayers}") + return Component.text("§6§l$serverTitle\n§r§7Online players: §f${server.onlinePlayers.size} / §d${server.maxPlayers}") } private fun getTabListFooter(): Component { - // Memory information val runtime = Runtime.getRuntime() val maxMemory = runtime.maxMemory() / (1024 * 1024) val allocatedMemory = runtime.totalMemory() / (1024 * 1024) val freeMemory = runtime.freeMemory() / (1024 * 1024) val usedMemory = allocatedMemory - freeMemory - // CPU usage - val osBean = ManagementFactory.getOperatingSystemMXBean() - var cpuLoad = osBean.systemLoadAverage - if (cpuLoad < 0) { - cpuLoad = osBean.availableProcessors * ManagementFactory.getThreadMXBean().threadCount / 100.0 - } - val tpsColor = when { - tps >= 18.0 -> "§2" // Dark green - tps >= 15.0 -> "§e" // Yellow - else -> "§c" // Red + tps >= 18.0 -> "§2" + tps >= 15.0 -> "§e" + else -> "§c" } return Component.text( - "\n§7RAM: §f${usedMemory}MB / ${maxMemory}MB" + - "\n§7CPU: §f${df.format(cpuLoad)}%" + - "\n§7TPS: $tpsColor${df.format(tps)}" + "\n§7RAM: §f${formatMemory(usedMemory)} / ${formatMemory(maxMemory)}" + + "\n§7CPU: ${formatCPU(cpuUsage)}%" + + "\n§7TPS: $tpsColor${df.format(tps)}" ) } fun getServerInfo(): String { - val info = StringBuilder() - - // Memory information val runtime = Runtime.getRuntime() val maxMemory = runtime.maxMemory() / (1024 * 1024) val allocatedMemory = runtime.totalMemory() / (1024 * 1024) val freeMemory = runtime.freeMemory() / (1024 * 1024) val usedMemory = allocatedMemory - freeMemory - // CPU usage - val osBean = ManagementFactory.getOperatingSystemMXBean() - var cpuLoad = osBean.systemLoadAverage - if (cpuLoad < 0) { - cpuLoad = osBean.availableProcessors * ManagementFactory.getThreadMXBean().threadCount / 100.0 + return buildString { + append("§6--- ServerInfo ---\n") + append("§6Title: §f$serverTitle\n") + append("§aRAM Usage: §f${formatMemory(usedMemory)} / ${formatMemory(allocatedMemory)}\n") + append("§aMax RAM: §f${formatMemory(maxMemory)}\n") + append("§aCPU Load: ${formatCPU(cpuUsage)}%\n") + append("§aTPS: ${formatTPS(tps)}\n") } - - // Format server info using string templates - info.append("§6===== SERVER INFO =====\n") - info.append("§6Title: §f$serverTitle\n") - info.append("§aRAM Usage: §f${usedMemory}MB / ${allocatedMemory}MB\n") - info.append("§aMax RAM: §f${maxMemory}MB\n") - info.append("§aCPU Load: §f${df.format(cpuLoad)}%\n") - info.append("§aTPS: ${formatTPS(tps)}\n") - - return info.toString() } private fun formatTPS(tps: Double): String { return when { - tps >= 18.0 -> "§2${df.format(tps)}" // Dark green - tps >= 15.0 -> "§e${df.format(tps)}" // Yellow - else -> "§c${df.format(tps)}" // Red + tps >= 18.0 -> "§2${df.format(tps)}" + tps >= 15.0 -> "§e${df.format(tps)}" + else -> "§c${df.format(tps)}" + } + } + + private fun formatMemory(valueInMB: Long): String { + return if (valueInMB >= 1000) { + val gb = valueInMB / 1024.0 + "${df.format(gb)} GB" + } else { + "$valueInMB MB" + } + } + + private fun formatCPU(cpu: Double): String { + return when { + cpu <= 30.0 -> "§2${df.format(cpu)}" + cpu <= 70.0 -> "§e${df.format(cpu)}" + else -> "§c${df.format(cpu)}" } } fun getPingForPlayer(player: Player): Int { return try { - // Get player ping using reflection val entityPlayer = player.javaClass.getMethod("getHandle").invoke(player) val pingField = entityPlayer.javaClass.getField("ping") pingField.getInt(entityPlayer) @@ -200,17 +240,14 @@ class Server_info : JavaPlugin() { -1 } } - - // Command executor inner class with tab completion + // Create CommandClass that stores the Commands that can be used private inner class ServerInfoCommand(private val plugin: Server_info) : CommandExecutor, TabCompleter { override fun onCommand(sender: CommandSender, cmd: Command, label: String, args: Array): Boolean { if (cmd.name.equals("serverinfo", ignoreCase = true)) { - // If there are arguments, handle them if (args.isNotEmpty()) { when (args[0].lowercase()) { "title", "settitle" -> { - // Check permission if (!sender.hasPermission("serverinfo.admin")) { sender.sendMessage("§cYou don't have permission to change the server title.") return true @@ -221,14 +258,12 @@ class Server_info : JavaPlugin() { return true } - // Join remaining args as the title val newTitle = args.copyOfRange(1, args.size).joinToString(" ") plugin.setServerTitle(newTitle) sender.sendMessage("§aServer title changed to: §f$newTitle") return true } "reload" -> { - // Check permission if (!sender.hasPermission("serverinfo.admin")) { sender.sendMessage("§cYou don't have permission to reload the plugin.") return true @@ -242,13 +277,12 @@ class Server_info : JavaPlugin() { } } - // Default behavior - show server info sender.sendMessage(plugin.getServerInfo()) - // If the sender is a player, show their ping if (sender is Player) { val ping = plugin.getPingForPlayer(sender) sender.sendMessage("§aYour Ping: §f${ping}ms") + sender.sendMessage("§6-- Powered by Kotlin --") } return true @@ -256,12 +290,7 @@ class Server_info : JavaPlugin() { return false } - override fun onTabComplete( - sender: CommandSender, - command: Command, - alias: String, - args: Array - ): List? { + override fun onTabComplete(sender: CommandSender, command: Command, alias: String, args: Array): List? { if (command.name.equals("serverinfo", ignoreCase = true)) { if (args.size == 1) { val completions = mutableListOf() diff --git a/target/classes/org/server_info/server_info/Server_info$ServerInfoCommand.class b/target/classes/org/server_info/server_info/Server_info$ServerInfoCommand.class index f862099..102a7a6 100644 Binary files a/target/classes/org/server_info/server_info/Server_info$ServerInfoCommand.class and b/target/classes/org/server_info/server_info/Server_info$ServerInfoCommand.class differ diff --git a/target/classes/org/server_info/server_info/Server_info$startTPSCalculation$1.class b/target/classes/org/server_info/server_info/Server_info$startTPSCalculation$1.class index 555ef8b..7a6116e 100644 Binary files a/target/classes/org/server_info/server_info/Server_info$startTPSCalculation$1.class and b/target/classes/org/server_info/server_info/Server_info$startTPSCalculation$1.class differ diff --git a/target/classes/org/server_info/server_info/Server_info$startTabListUpdater$1.class b/target/classes/org/server_info/server_info/Server_info$startTabListUpdater$1.class index 4dd44db..78f74c4 100644 Binary files a/target/classes/org/server_info/server_info/Server_info$startTabListUpdater$1.class and b/target/classes/org/server_info/server_info/Server_info$startTabListUpdater$1.class differ diff --git a/target/classes/org/server_info/server_info/Server_info.class b/target/classes/org/server_info/server_info/Server_info.class index 3419cbd..32b6c05 100644 Binary files a/target/classes/org/server_info/server_info/Server_info.class and b/target/classes/org/server_info/server_info/Server_info.class differ diff --git a/target/original-server_info-1.0-SNAPSHOT.jar b/target/original-server_info-1.0-SNAPSHOT.jar index 2f83d93..b732245 100644 Binary files a/target/original-server_info-1.0-SNAPSHOT.jar and b/target/original-server_info-1.0-SNAPSHOT.jar differ diff --git a/target/server_info-1.0-SNAPSHOT.jar b/target/server_info-1.0-SNAPSHOT.jar index de475b4..3422817 100644 Binary files a/target/server_info-1.0-SNAPSHOT.jar and b/target/server_info-1.0-SNAPSHOT.jar differ