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
- Implemented HCIM Rework
< --- 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.
@@ -1,6 +1,7 @@
package core.game.system;
import core.game.system.security.EncryptionManager;
import gui.ServerMonitor;
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.amsc.WorldCommunicator
import core.tools.TimeStamp
import gui.ServerMonitor
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
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.
* @author Emperor
* @author Vexia
* @author Ceikry
*/
object Server {
@@ -95,6 +95,7 @@ object Server {
GEAutoStock.autostock()
val scanner = Scanner(System.`in`)
running = true
GlobalScope.launch {
while(scanner.hasNextLine()){
@@ -112,7 +113,7 @@ object Server {
GlobalScope.launch {
delay(20000)
while(running){
if(System.currentTimeMillis() - lastHeartbeat > 1800 && running){
if(System.currentTimeMillis() - lastHeartbeat > 7200 && running){
SystemLogger.logErr("Triggering reboot due to heartbeat timeout")
SystemLogger.logErr("Creating thread dump...")
val dump = threadDump(true, true)
@@ -17,6 +17,9 @@ class ServerConstants {
@JvmField
var SHUTDOWN_HOOK: Thread = Thread(SystemShutdownHook())
@JvmField
var ALLOW_GUI: Boolean = false
@JvmField
var DATA_PATH: String? = null
@@ -209,6 +212,9 @@ class ServerConstants {
if(data.containsKey("writeLogs")){
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_USER = data["database_username"].toString()
@@ -374,7 +374,9 @@ class ScriptAPI(private val bot: Player) {
diffX /= 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 {
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.terminal.Terminal
import gui.GuiEvent
import gui.ServerMonitor
import rs09.ServerConstants
import java.io.*
import java.text.SimpleDateFormat
@@ -42,28 +44,48 @@ object SystemLogger {
@JvmStatic
fun logInfo(vararg messages: String){
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
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
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
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
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
@@ -115,6 +115,7 @@ class DisconnectionQueue {
*/
@JvmOverloads
fun add(player: Player, clear: Boolean = false) {
if(queue[player.name] != null) return
queue[player.name] = DisconnectionEntry(player, clear)
}
@@ -2,6 +2,8 @@ package rs09.net.packet
import core.net.packet.OutgoingPacket
import core.net.packet.out.*
import gui.GuiEvent
import gui.ServerMonitor
import rs09.game.system.SystemLogger
import java.util.*
@@ -28,6 +30,7 @@ object PacketWriteQueue {
@JvmStatic
fun flush(){
ServerMonitor.eventQueue.add(GuiEvent.UpdateQueuedPackets(PacketsToWrite.size))
while(!PacketsToWrite.isEmpty()){
val p = PacketsToWrite.poll() ?: continue
write(p.out,p.context)
@@ -5,12 +5,15 @@ import core.game.system.SystemManager
import core.game.system.SystemState
import core.game.system.task.Pulse
import core.plugin.CorePluginTypes.Managers
import gui.GuiEvent
import gui.ServerMonitor
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import rs09.Server
import rs09.ServerConstants
import rs09.ServerStore
import rs09.game.ai.general.GeneralBotCreator
import rs09.game.system.SystemLogger
import rs09.game.world.GameWorld
import rs09.game.world.repository.Repository
@@ -44,7 +47,25 @@ class MajorUpdateWorker {
//run our pulses
for(pulse in list) {
val b = System.currentTimeMillis()
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
@@ -58,7 +79,9 @@ class MajorUpdateWorker {
sequence.start()
sequence.run()
sequence.end()
PacketWriteQueue.flush()
GlobalScope.launch {
PacketWriteQueue.flush()
}
} catch (e: Exception){
e.printStackTrace()
}
@@ -95,12 +118,18 @@ class MajorUpdateWorker {
}
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))
}
}
fun start() {
if(!started){
worker.start()
}
if (ServerConstants.ALLOW_GUI)
ServerMonitor.open()
}
}
+3 -2
View File
@@ -16,7 +16,7 @@
"enable_bots": true,
"autostock_ge": true,
"allow_token_purchase": true,
"max_adv_bots": "0",
"max_adv_bots": "100",
"message_of_the_week_identifier": "0",
"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>"
@@ -45,7 +45,8 @@
"database_password": "",
"database_address": "127.0.0.1",
"database_port": "3306",
"ms_secret_key": "2009scape_development"
"ms_secret_key": "2009scape_development",
"enable_gui": false
},
"PluginToggles": {