Initial Commit of Gjallarhorn: A Heimdall Analytics Tool

This commit is contained in:
Kenneth Endfinger 2021-12-26 03:33:23 -05:00
parent cbbefc94a2
commit ff665c27f5
No known key found for this signature in database
GPG Key ID: C4E68E5647420E10
13 changed files with 257 additions and 6 deletions

View File

@ -39,6 +39,9 @@ tasks.create("updateManifests") {
writer.use {
val rootPath = rootProject.rootDir.toPath()
val updateManifest = subprojects.mapNotNull { project ->
if (project.name == "foundation-gjallarhorn") {
return@mapNotNull null
}
val files = project.tasks.getByName("shadowJar").outputs
val paths = files.files.map { rootPath.relativize(it.toPath()).toString() }
@ -119,8 +122,10 @@ subprojects {
}
}
tasks.withType<ShadowJar> {
archiveClassifier.set("plugin")
if (project.name != "foundation-gjallarhorn") {
tasks.withType<ShadowJar> {
archiveClassifier.set("plugin")
}
}
tasks.assemble {

View File

@ -0,0 +1,10 @@
dependencies {
implementation(project(":foundation-core"))
implementation(project(":foundation-heimdall"))
implementation("org.slf4j:slf4j-simple:1.7.32")
implementation("com.github.ajalt.clikt:clikt:3.3.0")
}
listOf(tasks.jar, tasks.shadowJar).map { it.get() }.forEach { task ->
task.manifest.attributes["Main-Class"] = "cloud.kubelet.foundation.gjallarhorn.MainKt"
}

View File

@ -0,0 +1,34 @@
package cloud.kubelet.foundation.gjallarhorn
import java.util.*
import kotlin.collections.HashMap
class BlockStateTracker {
val blocks = HashMap<BlockPosition, BlockState>()
fun place(position: BlockPosition, state: BlockState) {
blocks[position] = state
}
fun delete(position: BlockPosition) {
blocks.remove(position)
}
data class BlockState(val type: String)
data class BlockPosition(
val x: Long,
val y: Long,
val z: Long
) {
override fun equals(other: Any?): Boolean {
if (other !is BlockPosition) {
return false
}
return other.x == x && other.y == y && other.z == z
}
override fun hashCode(): Int = Objects.hash(x, y, z)
}
}

View File

@ -0,0 +1,22 @@
package cloud.kubelet.foundation.gjallarhorn
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.option
import org.jetbrains.exposed.sql.Database
class GjallarhornCommand : CliktCommand(invokeWithoutSubcommand = true) {
private val jdbcConnectionUrl by option("-c", "--connection-url", help = "JDBC Connection URL")
.default("jdbc:postgresql://localhost/foundation")
private val jdbcConnectionUsername by option("-u", "--connection-username", help = "JDBC Connection Username")
.default("jdbc:postgresql://localhost/foundation")
private val jdbcConnectionPassword by option("-p", "--connection-password", help = "JDBC Connection Passowrd")
.default("jdbc:postgresql://localhost/foundation")
override fun run() {
val db = Database.connect(jdbcConnectionUrl, user = jdbcConnectionUsername, password = jdbcConnectionPassword)
currentContext.findOrSetObject { db }
}
}

View File

@ -0,0 +1,49 @@
package cloud.kubelet.foundation.gjallarhorn.commands
import cloud.kubelet.foundation.gjallarhorn.BlockStateTracker
import cloud.kubelet.foundation.gjallarhorn.compose
import cloud.kubelet.foundation.heimdall.view.BlockChangeView
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.requireObject
import com.github.ajalt.clikt.parameters.options.option
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import java.time.Instant
class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-log") {
private val db by requireObject<Database>()
private val timeAsString by option("--time", help = "Replay Time")
override fun run() {
val filter = compose(
combine = { a, b -> a and b },
{ timeAsString != null } to { BlockChangeView.time lessEq Instant.parse(timeAsString) }
)
val tracker = BlockStateTracker()
transaction(db) {
BlockChangeView.select(filter).orderBy(BlockChangeView.time).forEach { row ->
val changeIsBreak = row[BlockChangeView.isBreak]
val x = row[BlockChangeView.x]
val y = row[BlockChangeView.y]
val z = row[BlockChangeView.z]
val block = row[BlockChangeView.block]
val location = BlockStateTracker.BlockPosition(x.toLong(), y.toLong(), z.toLong())
if (changeIsBreak) {
tracker.delete(location)
} else {
tracker.place(location, BlockStateTracker.BlockState(block))
}
}
}
println("x,y,z,block")
for ((position, block) in tracker.blocks) {
println("${position.x},${position.y},${position.z},${block.type}")
}
}
}

View File

@ -0,0 +1,49 @@
package cloud.kubelet.foundation.gjallarhorn.commands
import cloud.kubelet.foundation.gjallarhorn.compose
import cloud.kubelet.foundation.heimdall.table.PlayerPositionTable
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.requireObject
import com.github.ajalt.clikt.parameters.options.option
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.greaterEq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import java.time.Instant
import java.util.*
class PlayerPositionExport : CliktCommand(name = "export-player-positions", help = "Export Player Positions") {
private val db by requireObject<Database>()
private val playerIdString by option("--player", help = "Player ID")
private val startTimeString by option("--start-time", help = "Start Time")
private val endTimeString by option("--end-time", help = "End Time")
override fun run() {
val filter = compose(
combine = { a, b -> a and b },
{ startTimeString != null } to { PlayerPositionTable.time greaterEq Instant.parse(startTimeString) },
{ endTimeString != null } to { PlayerPositionTable.time lessEq Instant.parse(endTimeString) },
{ playerIdString != null } to { PlayerPositionTable.player eq UUID.fromString(playerIdString) }
)
println("time,player,world,x,y,z,pitch,yaw")
transaction(db) {
PlayerPositionTable.select(filter).orderBy(PlayerPositionTable.time).forEach { row ->
val time = row[PlayerPositionTable.time]
val player = row[PlayerPositionTable.player]
val world = row[PlayerPositionTable.world]
val x = row[PlayerPositionTable.x]
val y = row[PlayerPositionTable.y]
val z = row[PlayerPositionTable.z]
val pitch = row[PlayerPositionTable.pitch]
val yaw = row[PlayerPositionTable.yaw]
println("${time},${player},${world},${x},${y},${z},${pitch},${yaw}")
}
}
}
}

View File

@ -0,0 +1,40 @@
package cloud.kubelet.foundation.gjallarhorn
import cloud.kubelet.foundation.heimdall.table.PlayerSessionTable
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.requireObject
import com.github.ajalt.clikt.parameters.options.option
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.*
class PlayerSessionExport : CliktCommand(name = "export-player-sessions", help = "Export Player Sessions") {
private val db by requireObject<Database>()
private val playerIdString by option("--player-id", help = "Player ID")
private val playerNameString by option("--player-name", help = "Player Name")
override fun run() {
val filter = compose(
combine = { a, b -> a and b },
{ playerIdString != null } to { PlayerSessionTable.player eq UUID.fromString(playerIdString) },
{ playerNameString != null } to { PlayerSessionTable.name eq playerNameString!! }
)
println("id,player,name,start,end")
transaction(db) {
PlayerSessionTable.select(filter).orderBy(PlayerSessionTable.endTime).forEach { row ->
val id = row[PlayerSessionTable.id]
val player = row[PlayerSessionTable.player]
val name = row[PlayerSessionTable.name]
val start = row[PlayerSessionTable.startTime]
val end = row[PlayerSessionTable.endTime]
println("${id},${player},${name},${start},${end}")
}
}
}
}

View File

@ -0,0 +1,11 @@
package cloud.kubelet.foundation.gjallarhorn
import org.jetbrains.exposed.sql.Op
fun compose(
combine: (Op<Boolean>, Op<Boolean>) -> Op<Boolean>,
vararg filters: Pair<() -> Boolean, () -> Op<Boolean>>
): Op<Boolean> = filters.toMap().entries
.filter { it.key() }
.map { it.value() }
.fold(Op.TRUE as Op<Boolean>, combine)

View File

@ -0,0 +1,11 @@
package cloud.kubelet.foundation.gjallarhorn
import cloud.kubelet.foundation.gjallarhorn.commands.BlockLogReplay
import cloud.kubelet.foundation.gjallarhorn.commands.PlayerPositionExport
import com.github.ajalt.clikt.core.subcommands
fun main(args: Array<String>) = GjallarhornCommand().subcommands(
BlockLogReplay(),
PlayerSessionExport(),
PlayerPositionExport()
).main(args)

View File

@ -1,7 +1,7 @@
dependencies {
implementation("org.postgresql:postgresql:42.3.1")
implementation("org.jetbrains.exposed:exposed-jdbc:0.36.2")
implementation("org.jetbrains.exposed:exposed-java-time:0.36.2")
implementation("com.zaxxer:HikariCP:5.0.0")
api("org.postgresql:postgresql:42.3.1")
api("org.jetbrains.exposed:exposed-jdbc:0.36.2")
api("org.jetbrains.exposed:exposed-java-time:0.36.2")
api("com.zaxxer:HikariCP:5.0.0")
compileOnly(project(":foundation-core"))
}

View File

@ -0,0 +1,17 @@
package cloud.kubelet.foundation.heimdall.view
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.javatime.timestamp
object BlockChangeView : Table("block_changes") {
val isBreak = bool("break")
val time = timestamp("time")
val player = uuid("player")
val world = uuid("world")
val x = double("x")
val y = double("y")
val z = double("z")
val pitch = double("pitch")
val yaw = double("yaw")
val block = text("block")
}

View File

@ -118,3 +118,5 @@ create table if not exists heimdall.entity_kills (
);
--
select create_hypertable('heimdall.entity_kills', 'time', 'player', 4, if_not_exists => TRUE);
--
create or replace view heimdall.block_changes as select true as break, * from heimdall.block_breaks union all select false as break, * from heimdall.block_places;

View File

@ -4,4 +4,5 @@ include(
":foundation-core",
":foundation-bifrost",
":foundation-heimdall",
":foundation-gjallarhorn",
)