mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2026-08-28 05:45:10 -06:00
Performance tests
This commit is contained in:
parent
aa5f25fed4
commit
cdf43d01f8
1 changed files with 186 additions and 0 deletions
186
Server/src/test/kotlin/content/CombatPerformanceTests.kt
Normal file
186
Server/src/test/kotlin/content/CombatPerformanceTests.kt
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
package content
|
||||
|
||||
import TestUtils
|
||||
import core.game.node.entity.combat.CombatMovementIntents
|
||||
import core.game.node.entity.combat.equipment.WeaponInterface
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.skill.Skills
|
||||
import core.game.world.GameWorld
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.update.UpdateSequence
|
||||
import core.net.packet.PacketProcessor
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class CombatPerformanceTests {
|
||||
init {
|
||||
TestUtils.preTestSetup()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serverTickStaysWithinBudgetWithLiveSizedCombatLoad() {
|
||||
val load = createCombatLoad()
|
||||
try {
|
||||
assertEquals(REAL_PLAYER_COUNT, load.players.count { !it.isArtificial })
|
||||
assertEquals(BOT_PLAYER_COUNT, load.players.count { it.isArtificial })
|
||||
|
||||
repeat(WARMUP_TICKS) {
|
||||
measureCombatTick(load, it)
|
||||
}
|
||||
|
||||
val durations = LongArray(MEASURED_TICKS) { tick ->
|
||||
measureCombatTick(load, tick + WARMUP_TICKS)
|
||||
}
|
||||
val sorted = durations.sorted()
|
||||
val p90Index = ((sorted.size * 9 + 9) / 10 - 1).coerceIn(0, sorted.lastIndex)
|
||||
val p90 = sorted[p90Index]
|
||||
val max = sorted.last()
|
||||
val durationText = durations.joinToString(prefix = "[", postfix = "]")
|
||||
|
||||
assertTrue(
|
||||
p90 <= HEADROOM_TICK_BUDGET_MILLIS,
|
||||
"650-player combat p90 tick time should leave room for slower live hardware. " +
|
||||
"durations=${durationText}ms, p90=${p90}ms, " +
|
||||
"budget=${HEADROOM_TICK_BUDGET_MILLIS}ms"
|
||||
)
|
||||
assertTrue(
|
||||
max <= LIVE_TICK_BUDGET_MILLIS,
|
||||
"650-player combat tick should remain under the live 600ms server tick budget. " +
|
||||
"durations=${durationText}ms, max=${max}ms"
|
||||
)
|
||||
} finally {
|
||||
load.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCombatLoad(): CombatLoad {
|
||||
val players = ArrayList<Player>(TOTAL_PLAYER_COUNT)
|
||||
val closeables = ArrayList<AutoCloseable>(TOTAL_PLAYER_COUNT)
|
||||
val previousWildPvp = GameWorld.settings!!.wild_pvp_enabled
|
||||
|
||||
for (i in 0 until TOTAL_PLAYER_COUNT) {
|
||||
val player = TestUtils.getMockPlayer("combat_perf_$i", isBot = i >= REAL_PLAYER_COUNT)
|
||||
players.add(player)
|
||||
closeables.add(player)
|
||||
configureMeleePlayer(player)
|
||||
}
|
||||
|
||||
val pairs = players.chunked(2).mapIndexed { index, pair ->
|
||||
CombatPair(pair[0], pair[1], pairOrigin(index))
|
||||
}
|
||||
val load = CombatLoad(players, closeables, pairs, previousWildPvp)
|
||||
|
||||
GameWorld.settings!!.wild_pvp_enabled = true
|
||||
load.resetPairPositionsAndMovement(0)
|
||||
for (pair in pairs) {
|
||||
pair.first.attack(pair.second)
|
||||
pair.second.attack(pair.first)
|
||||
}
|
||||
return load
|
||||
}
|
||||
|
||||
private fun measureCombatTick(load: CombatLoad, tick: Int): Long {
|
||||
PacketProcessor.queue.clear()
|
||||
load.resetPairPositionsAndMovement(tick)
|
||||
load.requestAllCombatMovement()
|
||||
|
||||
assertEquals(
|
||||
TOTAL_PLAYER_COUNT,
|
||||
CombatMovementIntents.pendingCount(),
|
||||
"The performance fixture should exercise one combat movement intent per loaded player."
|
||||
)
|
||||
|
||||
val start = System.nanoTime()
|
||||
GameWorld.majorUpdateWorker.handleTickActions(false)
|
||||
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)
|
||||
}
|
||||
|
||||
private fun configureMeleePlayer(player: Player) {
|
||||
player.properties.attackStyle = WeaponInterface.AttackStyle(
|
||||
WeaponInterface.STYLE_AGGRESSIVE,
|
||||
WeaponInterface.BONUS_CRUSH
|
||||
)
|
||||
player.properties.combatPulse.updateStyle()
|
||||
player.properties.combatLevel = 126
|
||||
player.skills.setStaticLevel(Skills.HITPOINTS, 10_000)
|
||||
player.skills.lifepoints = 10_000
|
||||
player.settings.runEnergy = 100.0
|
||||
player.settings.setRunToggled(true)
|
||||
player.skullManager.isWilderness = true
|
||||
player.skullManager.level = 50
|
||||
}
|
||||
|
||||
private data class CombatPair(
|
||||
val first: Player,
|
||||
val second: Player,
|
||||
val origin: Location
|
||||
)
|
||||
|
||||
private class CombatLoad(
|
||||
val players: List<Player>,
|
||||
private val closeables: List<AutoCloseable>,
|
||||
private val pairs: List<CombatPair>,
|
||||
private val previousWildPvp: Boolean
|
||||
) : AutoCloseable {
|
||||
|
||||
fun resetPairPositionsAndMovement(tick: Int) {
|
||||
val direction = if (tick % 2 == 0) 1 else -1
|
||||
for (pair in pairs) {
|
||||
val firstLocation = pair.origin
|
||||
val secondLocation = pair.origin.transform(1, 0, 0)
|
||||
place(pair.first, firstLocation)
|
||||
place(pair.second, secondLocation)
|
||||
queueRun(pair.first, firstLocation.transform(-8 * direction, 0, 0))
|
||||
queueRun(pair.second, secondLocation.transform(8 * direction, 0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
fun requestAllCombatMovement() {
|
||||
CombatMovementIntents.clear()
|
||||
for (pair in pairs) {
|
||||
CombatMovementIntents.request(pair.first, pair.second)
|
||||
CombatMovementIntents.request(pair.second, pair.first)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
CombatMovementIntents.clear()
|
||||
for (closeable in closeables.asReversed()) {
|
||||
closeable.close()
|
||||
}
|
||||
GameWorld.Pulser.updateAll()
|
||||
UpdateSequence.renderablePlayers.sync()
|
||||
PacketProcessor.queue.clear()
|
||||
GameWorld.settings!!.wild_pvp_enabled = previousWildPvp
|
||||
}
|
||||
|
||||
private fun place(player: Player, location: Location) {
|
||||
player.location = location
|
||||
player.walkingQueue.reset()
|
||||
}
|
||||
|
||||
private fun queueRun(player: Player, destination: Location) {
|
||||
player.walkingQueue.reset(true)
|
||||
player.walkingQueue.addPath(destination.x, destination.y)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val REAL_PLAYER_COUNT = 150
|
||||
const val BOT_PLAYER_COUNT = 500
|
||||
const val TOTAL_PLAYER_COUNT = REAL_PLAYER_COUNT + BOT_PLAYER_COUNT
|
||||
const val WARMUP_TICKS = 3
|
||||
const val MEASURED_TICKS = 10
|
||||
const val LIVE_TICK_BUDGET_MILLIS = 600L
|
||||
const val HEADROOM_TICK_BUDGET_MILLIS = 350L
|
||||
|
||||
fun pairOrigin(index: Int): Location {
|
||||
val columns = 25
|
||||
val column = index % columns
|
||||
val row = index / columns
|
||||
return Location.create(3200 + column * 12, 3600 + row * 6, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue