Merge branch 'server-gui' into 'master'

Server Performance Monitor, Plethora of Performance Improvements

See merge request 2009scape/2009scape!349
This commit is contained in:
Ceikry
2021-12-19 03:06:17 +00:00
14 changed files with 1018 additions and 851 deletions
+1
View File
@@ -74,4 +74,5 @@
- Fix granting hp xp on controlled - Fix granting hp xp on controlled
- Implemented HCIM Rework - Implemented HCIM Rework
< --- ABOVE Released December 15, 2021 https://gitlab.com/2009scape/2009scape/-/tags/Dec-15-2021 ---- > < --- ABOVE Released December 15, 2021 https://gitlab.com/2009scape/2009scape/-/tags/Dec-15-2021 ---- >
- Implemented server performance monitor, many performance improvements
- Fishing and fishing trawler now use closer-to-empirical formulae. - Fishing and fishing trawler now use closer-to-empirical formulae.
@@ -1,6 +1,7 @@
package core.game.system; package core.game.system;
import core.game.system.security.EncryptionManager; import core.game.system.security.EncryptionManager;
import gui.ServerMonitor;
import rs09.game.world.GameWorld; import rs09.game.world.GameWorld;
/** /**
+132
View File
@@ -0,0 +1,132 @@
package gui
import java.awt.*
import javax.swing.JPanel
/**
* @author Rodrigo, Maritaria
* @author Ceikry (rewrote into Kotlin, adapted for specific use case)
*/
class GraphView() : JPanel() {
private val values: MutableList<Int> = ArrayList(10)
fun setValues(newValues: Collection<Int>) {
values.clear()
addValues(newValues)
}
fun addValues(newValues: Collection<Int>) {
if(values.size > 200) values.removeFirst()
values.addAll(newValues)
updateUI()
}
override fun paintComponent(graphics: Graphics) {
super.paintComponent(graphics)
if (graphics !is Graphics2D) {
graphics.drawString("Graphics is not Graphics2D, unable to render", 0, 0)
return
}
val g: Graphics2D = graphics
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
val length: Int = values.size
val width: Int = width
val height: Int = height
val maxScore: Double = maxScore
val minScore: Double = minScore
val scoreRange: Double = maxScore - minScore
// draw white background
g.color = Color.WHITE
g.fillRect(
padding + labelPadding,
padding,
width - (2 * padding) - labelPadding,
height - (2 * padding) - labelPadding
)
g.color = Color.BLACK
val fontMetrics: FontMetrics = g.fontMetrics
val fontHeight: Int = fontMetrics.height
// create hatch marks and grid lines for y axis.
for (i in 0 until numberYDivisions + 1) {
val x1: Int = padding + labelPadding
val x2: Int = pointWidth + padding + labelPadding
val y: Int =
height - (((i * (height - (padding * 2) - labelPadding)) / numberYDivisions) + padding + labelPadding)
if (length > 0) {
g.color = gridColor
g.drawLine(padding + labelPadding + 1 + pointWidth, y, width - padding, y)
g.color = Color.BLACK
val tickValue: Int = (minScore + ((scoreRange * i) / numberYDivisions)).toInt()
val yLabel: String = tickValue.toString() + ""
val labelWidth: Int = fontMetrics.stringWidth(yLabel)
g.drawString(yLabel, x1 - labelWidth - 5, y + (fontHeight / 2) - 3)
}
g.drawLine(x1, y, x2, y)
}
// create x and y axes
g.drawLine(padding + labelPadding, height - padding - labelPadding, padding + labelPadding, padding)
g.drawLine(
padding + labelPadding,
height - padding - labelPadding,
width - padding,
height - padding - labelPadding
)
val oldStroke: Stroke = g.stroke
g.color = lineColor
g.stroke = graphStroke
val xScale: Double = (width.toDouble() - (2 * padding) - labelPadding) / (length - 1)
val yScale: Double = (height.toDouble() - (2 * padding) - labelPadding) / scoreRange
val graphPoints: MutableList<Point> = ArrayList(length)
for (i in 0 until length) {
val x1: Int = ((i * xScale) + padding + labelPadding).toInt()
val y1: Int = ((maxScore - values.get(i)) * yScale + padding).toInt()
graphPoints.add(Point(x1, y1))
}
for (i in 0 until graphPoints.size - 1) {
val x1: Int = graphPoints[i].x
val y1: Int = graphPoints[i].y
val x2: Int = graphPoints[i + 1].x
val y2: Int = graphPoints[i + 1].y
if(y2 != 0) {
g.drawLine(x1, y1, x2, y2)
}
}
val drawDots: Boolean = width > (length * pointWidth)
if (drawDots) {
g.stroke = oldStroke
g.color = pointColor
for (graphPoint: Point in graphPoints) {
val x: Int = graphPoint.x - pointWidth / 2
val y: Int = graphPoint.y - pointWidth / 2
g.fillOval(x, y, pointWidth, pointWidth)
}
}
}
private val minScore: Double
get() = values.stream().min { obj: Int, integer: Int? -> obj.compareTo((integer)!!) }.orElse(0)
.toDouble()
private val maxScore: Double
get() {
return values.stream().max { obj: Int, integer: Int? -> obj.compareTo((integer)!!) }.orElse(0)
.toDouble()
}
companion object {
private const val padding: Int = 25
private const val labelPadding: Int = 25
private const val pointWidth: Int = 4
private const val numberYDivisions: Int = 10
private val lineColor: Color = Color(44, 102, 230, 180)
private val pointColor: Color = Color(100, 100, 100, 180)
private val gridColor: Color = Color(200, 200, 200, 200)
private val graphStroke: Stroke = BasicStroke(2f)
}
init {
preferredSize = Dimension(padding * 2 + 300, padding * 2 + 200)
}
}
+10
View File
@@ -0,0 +1,10 @@
package gui
sealed class GuiEvent {
class UpdatePulseCount(val amount: Int) : GuiEvent()
class UpdateTickTime(val time: Long) : GuiEvent()
class UpdateQueuedPackets(val amount: Int) : GuiEvent()
class AddDebugMessage(val message: String) : GuiEvent()
class AddDefaultMessage(val message: String) : GuiEvent()
class AddAIPMessage(val message: String): GuiEvent()
}
+171
View File
@@ -0,0 +1,171 @@
package gui
import rs09.game.system.SystemLogger
import rs09.game.world.GameWorld
import java.awt.BorderLayout
import java.awt.Component
import java.awt.Dimension
import javax.swing.*
import javax.swing.border.Border
object ServerMonitor : JFrame("Server Monitor"){
val eventQueue = ArrayList<GuiEvent>()
val pulseCount = JLabel("0")
val tickTime = JLabel("0")
val queuedPackets = JLabel("0")
val pulseList = JTextArea("")
val debugLog = JTextArea("")
val defaultLog = JTextArea("")
val AIPlog = JTextArea("")
val tickTimeGraph = GraphView()
val pulseCountGraph = GraphView()
val packetQueueGraph = GraphView()
init {
isVisible = false
layout = BorderLayout()
size = Dimension(800,640)
preferredSize = Dimension(800,640)
minimumSize = Dimension(800,640)
isResizable = false
val tabs = JTabbedPane()
tabs.addTab("Overview", constructOverviewTab())
tabs.addTab("Graphs", constructGraphsTab())
add(tabs)
pack()
}
private fun constructGraphsTab(): JPanel {
val graphPanel = JPanel()
graphPanel.placeAt(0,10,800,590)
val tickTimeBorder = BorderFactory.createTitledBorder("Tick Time")
val pulseCountBorder = BorderFactory.createTitledBorder("Pulse Count")
val packetQueueBorder = BorderFactory.createTitledBorder("Queued Packets")
tickTimeGraph.placeAt(0, 20, 300, 200)
tickTimeGraph.border = tickTimeBorder
pulseCountGraph.placeAt(300,20,300,200)
pulseCountGraph.border = pulseCountBorder
packetQueueGraph.placeAt(0,220,300,200)
packetQueueGraph.border = packetQueueBorder
graphPanel.add(tickTimeGraph)
graphPanel.add(pulseCountGraph)
graphPanel.add(packetQueueGraph)
return graphPanel
}
private fun constructOverviewTab(): JPanel {
val overviewPanel = JPanel(null)
overviewPanel.placeAt(0,10,800,590)
val pulseLabel = JLabel("Pulse Count: ")
pulseLabel.placeAt(0,20,100,10)
pulseCount.placeAt(150,20,100,10)
val tickLabel = JLabel("Tick Time: ")
tickLabel.placeAt(0,35,100,10)
tickTime.placeAt(150,35,100,10)
val packetLabel = JLabel("Queued Packets: ")
packetLabel.placeAt(0,50,200,10)
queuedPackets.placeAt(150, 50, 100, 10)
val scrollPane = JScrollPane(pulseList)
scrollPane.placeAt(400,10,390,275)
val border = BorderFactory.createTitledBorder("Pulses")
scrollPane.border = border
overviewPanel.add(pulseLabel)
overviewPanel.add(pulseCount)
overviewPanel.add(tickLabel)
overviewPanel.add(tickTime)
overviewPanel.add(packetLabel)
overviewPanel.add(queuedPackets)
overviewPanel.add(scrollPane)
val logPane = constructLogPane()
logPane.placeAt(0,280,800,300)
overviewPanel.add(logPane)
return overviewPanel
}
private fun constructLogPane() : JTabbedPane {
val logPane = JTabbedPane()
debugLog.isEditable = false
val debugPanel = JPanel(BorderLayout())
val debugScrollpane = JScrollPane(debugLog)
debugScrollpane.placeAt(0,300,800,300)
debugPanel.add(debugScrollpane, BorderLayout.CENTER)
defaultLog.isEditable = false
val defaultPanel = JPanel(BorderLayout())
val defaultScrollpane = JScrollPane(defaultLog)
defaultScrollpane.placeAt(0,300,800,300)
defaultPanel.add(defaultScrollpane, BorderLayout.CENTER)
AIPlog.isEditable = false
val AIPPanel = JPanel(BorderLayout())
val AIPscrollpane = JScrollPane(AIPlog)
AIPscrollpane.placeAt(0,300,800,300)
AIPPanel.add(AIPscrollpane, BorderLayout.CENTER)
logPane.addTab("Default",defaultPanel)
logPane.addTab("Debug",debugPanel)
logPane.addTab("AIP",AIPPanel)
return logPane
}
private fun Component.placeAt(x: Int, y: Int, width: Int, height: Int){
this.setBounds(x,y,width,height)
}
fun open() {
isVisible = true
Thread() {
while (true) {
SwingUtilities.invokeLater {
val removeList = ArrayList<GuiEvent>()
eventQueue.toTypedArray().forEach { event ->
when (event) {
is GuiEvent.UpdatePulseCount -> {
pulseCount.text = event.amount.toString()
pulseCountGraph.addValues(intArrayOf(event.amount).toList())
}
is GuiEvent.UpdateTickTime -> {
tickTime.text = event.time.toString() + "ms"
tickTimeGraph.addValues(intArrayOf(event.time.toInt()).toList())
}
is GuiEvent.AddDefaultMessage -> defaultLog.text = defaultLog.text + event.message + "\n"
is GuiEvent.AddAIPMessage -> AIPlog.text = AIPlog.text + event.message + "\n"
is GuiEvent.AddDebugMessage -> debugLog.text = debugLog.text + event.message + "\n"
is GuiEvent.UpdateQueuedPackets -> {
queuedPackets.text = event.amount.toString()
packetQueueGraph.addValues(intArrayOf(event.amount).toList())
}
}
removeList.add(event)
}
pulseList.text = GameWorld.Pulser.TASKS.toArray().filterNotNull().map { it.javaClass.simpleName + "\n" }
.filter { it.replace("\n", "").isNotEmpty() }.sorted().joinToString("")
eventQueue.removeAll(removeList)
repaint()
}
Thread.sleep(600L)
}
}.start()
}
}
+3 -2
View File
@@ -7,6 +7,7 @@ import core.gui.ConsoleFrame
import core.net.NioReactor import core.net.NioReactor
import core.net.amsc.WorldCommunicator import core.net.amsc.WorldCommunicator
import core.tools.TimeStamp import core.tools.TimeStamp
import gui.ServerMonitor
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -28,7 +29,6 @@ import kotlin.system.exitProcess
/** /**
* The main class, for those that are unable to read the class' name. * The main class, for those that are unable to read the class' name.
* @author Emperor * @author Emperor
* @author Vexia
* @author Ceikry * @author Ceikry
*/ */
object Server { object Server {
@@ -95,6 +95,7 @@ object Server {
GEAutoStock.autostock() GEAutoStock.autostock()
val scanner = Scanner(System.`in`) val scanner = Scanner(System.`in`)
running = true running = true
GlobalScope.launch { GlobalScope.launch {
while(scanner.hasNextLine()){ while(scanner.hasNextLine()){
@@ -112,7 +113,7 @@ object Server {
GlobalScope.launch { GlobalScope.launch {
delay(20000) delay(20000)
while(running){ while(running){
if(System.currentTimeMillis() - lastHeartbeat > 1800 && running){ if(System.currentTimeMillis() - lastHeartbeat > 7200 && running){
SystemLogger.logErr("Triggering reboot due to heartbeat timeout") SystemLogger.logErr("Triggering reboot due to heartbeat timeout")
SystemLogger.logErr("Creating thread dump...") SystemLogger.logErr("Creating thread dump...")
val dump = threadDump(true, true) val dump = threadDump(true, true)
@@ -17,6 +17,9 @@ class ServerConstants {
@JvmField @JvmField
var SHUTDOWN_HOOK: Thread = Thread(SystemShutdownHook()) var SHUTDOWN_HOOK: Thread = Thread(SystemShutdownHook())
@JvmField
var ALLOW_GUI: Boolean = false
@JvmField @JvmField
var DATA_PATH: String? = null var DATA_PATH: String? = null
@@ -209,6 +212,9 @@ class ServerConstants {
if(data.containsKey("writeLogs")){ if(data.containsKey("writeLogs")){
WRITE_LOGS = data["writeLogs"] as Boolean WRITE_LOGS = data["writeLogs"] as Boolean
} }
if(data.containsKey("enable_gui")){
ALLOW_GUI = data["enable_gui"] as Boolean
}
DATABASE_NAME = data["database_name"].toString() DATABASE_NAME = data["database_name"].toString()
DATABASE_USER = data["database_username"].toString() DATABASE_USER = data["database_username"].toString()
@@ -374,7 +374,9 @@ class ScriptAPI(private val bot: Player) {
diffX /= 2 diffX /= 2
diffY /= 2 diffY /= 2
} }
GameWorld.Pulser.submit(object : MovementPulse(bot, bot.location.transform(diffX, diffY, 0), Pathfinder.SMART) { val dest = bot.location.transform(diffX,diffY,0)
bot.pulseManager.run(object :
MovementPulse(bot, dest, Pathfinder.SMART) {
override fun pulse(): Boolean { override fun pulse(): Boolean {
return true return true
} }
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,8 @@ package rs09.game.system
import com.github.ajalt.mordant.rendering.TextColors import com.github.ajalt.mordant.rendering.TextColors
import com.github.ajalt.mordant.terminal.Terminal import com.github.ajalt.mordant.terminal.Terminal
import gui.GuiEvent
import gui.ServerMonitor
import rs09.ServerConstants import rs09.ServerConstants
import java.io.* import java.io.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@@ -42,28 +44,48 @@ object SystemLogger {
@JvmStatic @JvmStatic
fun logInfo(vararg messages: String){ fun logInfo(vararg messages: String){
for(m in messages){ for(m in messages){
if(m.isNotBlank()) t.println("${getTime()}: [INFO] $m") val msg = "${getTime()}: [INFO] $m"
if(m.isNotBlank()) {
t.println(msg)
ServerMonitor.eventQueue.add(GuiEvent.AddDefaultMessage(msg))
}
} }
} }
@JvmStatic @JvmStatic
fun logErr(message: String){ fun logErr(message: String){
if(message.isNotBlank()) t.println("${getTime()}: ${TextColors.brightRed("[ ERR] $message")}") val msg = "${getTime()}: [ ERR] $message"
if(message.isNotBlank()) {
t.println(msg)
ServerMonitor.eventQueue.add(GuiEvent.AddDebugMessage(msg))
}
} }
@JvmStatic @JvmStatic
fun logWarn(message: String){ fun logWarn(message: String){
if(message.isNotBlank()) t.println("${getTime()}: ${TextColors.yellow("[WARN] $message")}") val msg = "${getTime()}: [WARN] $message"
if(message.isNotBlank()) {
t.println(msg)
ServerMonitor.eventQueue.add(GuiEvent.AddDebugMessage(msg))
}
} }
@JvmStatic @JvmStatic
fun logAlert(message: String){ fun logAlert(message: String){
if(message.isNotBlank()) t.println("${getTime()}: ${TextColors.brightYellow("[ALRT] $message")}") val msg = "${getTime()}: [ALRT] $message"
if(message.isNotBlank()) {
t.println(msg)
ServerMonitor.eventQueue.add(GuiEvent.AddDebugMessage(msg))
}
} }
@JvmStatic @JvmStatic
fun logAI(message: String){ fun logAI(message: String){
if(message.isNotBlank()) t.println("${getTime()}: ${TextColors.gray("[AIPL] $message")}") val msg = "${getTime()}: [AIPL] $message"
if(message.isNotBlank()) {
t.println(msg)
ServerMonitor.eventQueue.add(GuiEvent.AddAIPMessage(msg))
}
} }
@JvmStatic @JvmStatic
@@ -115,6 +115,7 @@ class DisconnectionQueue {
*/ */
@JvmOverloads @JvmOverloads
fun add(player: Player, clear: Boolean = false) { fun add(player: Player, clear: Boolean = false) {
if(queue[player.name] != null) return
queue[player.name] = DisconnectionEntry(player, clear) queue[player.name] = DisconnectionEntry(player, clear)
} }
@@ -2,6 +2,8 @@ package rs09.net.packet
import core.net.packet.OutgoingPacket import core.net.packet.OutgoingPacket
import core.net.packet.out.* import core.net.packet.out.*
import gui.GuiEvent
import gui.ServerMonitor
import rs09.game.system.SystemLogger import rs09.game.system.SystemLogger
import java.util.* import java.util.*
@@ -28,6 +30,7 @@ object PacketWriteQueue {
@JvmStatic @JvmStatic
fun flush(){ fun flush(){
ServerMonitor.eventQueue.add(GuiEvent.UpdateQueuedPackets(PacketsToWrite.size))
while(!PacketsToWrite.isEmpty()){ while(!PacketsToWrite.isEmpty()){
val p = PacketsToWrite.poll() ?: continue val p = PacketsToWrite.poll() ?: continue
write(p.out,p.context) write(p.out,p.context)
@@ -5,12 +5,15 @@ import core.game.system.SystemManager
import core.game.system.SystemState import core.game.system.SystemState
import core.game.system.task.Pulse import core.game.system.task.Pulse
import core.plugin.CorePluginTypes.Managers import core.plugin.CorePluginTypes.Managers
import gui.GuiEvent
import gui.ServerMonitor
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import rs09.Server import rs09.Server
import rs09.ServerConstants import rs09.ServerConstants
import rs09.ServerStore import rs09.ServerStore
import rs09.game.ai.general.GeneralBotCreator
import rs09.game.system.SystemLogger import rs09.game.system.SystemLogger
import rs09.game.world.GameWorld import rs09.game.world.GameWorld
import rs09.game.world.repository.Repository import rs09.game.world.repository.Repository
@@ -44,7 +47,25 @@ class MajorUpdateWorker {
//run our pulses //run our pulses
for(pulse in list) { for(pulse in list) {
val b = System.currentTimeMillis()
if (pulse == null || pulse.update()) rmlist.add(pulse) if (pulse == null || pulse.update()) rmlist.add(pulse)
val time = System.currentTimeMillis() - b
if(time >= 100){
if(pulse is GeneralBotCreator.BotScriptPulse){
SystemLogger.logWarn("CRITICALLY Long Botscript Tick: ${pulse.botScript.javaClass.name} - $time ms")
} else {
SystemLogger.logWarn("CRITICALLY long running pulse: ${pulse.javaClass.name} - $time ms")
}
}
else if(time >= 30){
if(pulse is GeneralBotCreator.BotScriptPulse){
SystemLogger.logWarn("Long Botscript Tick: ${pulse.botScript.javaClass.name} - $time ms")
} else {
SystemLogger.logWarn("Long Running Pulse: ${pulse.javaClass.name} - $time ms")
}
}
} }
//remove all null or finished pulses from the list //remove all null or finished pulses from the list
@@ -58,7 +79,9 @@ class MajorUpdateWorker {
sequence.start() sequence.start()
sequence.run() sequence.run()
sequence.end() sequence.end()
PacketWriteQueue.flush() GlobalScope.launch {
PacketWriteQueue.flush()
}
} catch (e: Exception){ } catch (e: Exception){
e.printStackTrace() e.printStackTrace()
} }
@@ -95,12 +118,18 @@ class MajorUpdateWorker {
} }
val end = System.currentTimeMillis() val end = System.currentTimeMillis()
ServerMonitor.eventQueue.add(GuiEvent.UpdateTickTime(end - start))
ServerMonitor.eventQueue.add(GuiEvent.UpdatePulseCount(GameWorld.Pulser.TASKS.size))
Thread.sleep(max(600 - (end - start), 0)) Thread.sleep(max(600 - (end - start), 0))
} }
} }
fun start() { fun start() {
if(!started){ if(!started){
worker.start() worker.start()
} }
if (ServerConstants.ALLOW_GUI)
ServerMonitor.open()
} }
} }
+3 -2
View File
@@ -16,7 +16,7 @@
"enable_bots": true, "enable_bots": true,
"autostock_ge": true, "autostock_ge": true,
"allow_token_purchase": true, "allow_token_purchase": true,
"max_adv_bots": "0", "max_adv_bots": "100",
"message_of_the_week_identifier": "0", "message_of_the_week_identifier": "0",
"skillcape_perks": false, "skillcape_perks": false,
"message_of_the_week_text": "Welcome to 2009Scape! <br><col=11ff00>N</col><col=ecff00>o</col><col=ff8300>w</col <col=ff0000>i</col><col=ff00d4>n</col> <col=6100ff>T</col><col=000fff>e</col><col=00f0ff>c</col><col=00ff59>h</col><col=93ff00>n</col><col=ff9e00>i</col><col=ff0000>c</col><col=ff00d1>o</col><col=2700ff>l</col><col=00f7ff>o</col><col=00ff0c>r</col><col=ff0000>!</col>" "message_of_the_week_text": "Welcome to 2009Scape! <br><col=11ff00>N</col><col=ecff00>o</col><col=ff8300>w</col <col=ff0000>i</col><col=ff00d4>n</col> <col=6100ff>T</col><col=000fff>e</col><col=00f0ff>c</col><col=00ff59>h</col><col=93ff00>n</col><col=ff9e00>i</col><col=ff0000>c</col><col=ff00d1>o</col><col=2700ff>l</col><col=00f7ff>o</col><col=00ff0c>r</col><col=ff0000>!</col>"
@@ -45,7 +45,8 @@
"database_password": "", "database_password": "",
"database_address": "127.0.0.1", "database_address": "127.0.0.1",
"database_port": "3306", "database_port": "3306",
"ms_secret_key": "2009scape_development" "ms_secret_key": "2009scape_development",
"enable_gui": false
}, },
"PluginToggles": { "PluginToggles": {