Heimdall: Player Session Tracking

This commit is contained in:
Kenneth Endfinger 2021-12-24 02:42:13 -05:00
parent 139249c1de
commit 1985b3c507
No known key found for this signature in database
GPG Key ID: C4E68E5647420E10
6 changed files with 91 additions and 13 deletions

View File

@ -7,20 +7,25 @@ import cloud.kubelet.foundation.heimdall.buffer.EventBuffer
import cloud.kubelet.foundation.heimdall.event.BlockBreak import cloud.kubelet.foundation.heimdall.event.BlockBreak
import cloud.kubelet.foundation.heimdall.event.BlockPlace import cloud.kubelet.foundation.heimdall.event.BlockPlace
import cloud.kubelet.foundation.heimdall.event.PlayerPosition import cloud.kubelet.foundation.heimdall.event.PlayerPosition
import cloud.kubelet.foundation.heimdall.event.PlayerSession
import cloud.kubelet.foundation.heimdall.model.HeimdallConfig import cloud.kubelet.foundation.heimdall.model.HeimdallConfig
import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.Yaml
import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.event.block.BlockBreakEvent import org.bukkit.event.block.BlockBreakEvent
import org.bukkit.event.block.BlockPlaceEvent import org.bukkit.event.block.BlockPlaceEvent
import org.bukkit.event.entity.EntityDeathEvent import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerMoveEvent import org.bukkit.event.player.PlayerMoveEvent
import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.postgresql.Driver import org.postgresql.Driver
import java.lang.Exception import java.time.Instant
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.io.path.inputStream import kotlin.io.path.inputStream
class FoundationHeimdallPlugin : JavaPlugin(), Listener { class FoundationHeimdallPlugin : JavaPlugin(), Listener {
@ -31,6 +36,8 @@ class FoundationHeimdallPlugin : JavaPlugin(), Listener {
private val buffer = EventBuffer() private val buffer = EventBuffer()
private val bufferFlushThread = BufferFlushThread(this, buffer) private val bufferFlushThread = BufferFlushThread(this, buffer)
private val playerJoinTimes = ConcurrentHashMap<UUID, Instant>()
override fun onEnable() { override fun onEnable() {
val foundation = server.pluginManager.getPlugin("Foundation") as FoundationCorePlugin val foundation = server.pluginManager.getPlugin("Foundation") as FoundationCorePlugin
@ -91,7 +98,25 @@ class FoundationHeimdallPlugin : JavaPlugin(), Listener {
@EventHandler @EventHandler
fun onBlockBroken(event: BlockBreakEvent) = buffer.push(BlockBreak(event)) fun onBlockBroken(event: BlockBreakEvent) = buffer.push(BlockBreak(event))
@EventHandler
fun onPlayerJoin(event: PlayerJoinEvent) {
playerJoinTimes[event.player.uniqueId] = Instant.now()
}
@EventHandler(priority = EventPriority.HIGHEST)
fun onPlayerQuit(event: PlayerQuitEvent) {
val startTime = playerJoinTimes.remove(event.player.uniqueId) ?: return
val endTime = Instant.now()
buffer.push(PlayerSession(event.player.uniqueId, event.player.name, startTime, endTime))
}
override fun onDisable() { override fun onDisable() {
bufferFlushThread.stop() bufferFlushThread.stop()
val endTime = Instant.now()
for (playerId in playerJoinTimes.keys().toList()) {
val startTime = playerJoinTimes.remove(playerId) ?: continue
buffer.push(PlayerSession(playerId, server.getPlayer(playerId)?.name ?: "__unknown__", startTime, endTime))
}
bufferFlushThread.flush()
} }
} }

View File

@ -6,22 +6,14 @@ import java.util.concurrent.atomic.AtomicBoolean
class BufferFlushThread(val plugin: FoundationHeimdallPlugin, val buffer: EventBuffer) { class BufferFlushThread(val plugin: FoundationHeimdallPlugin, val buffer: EventBuffer) {
private val running = AtomicBoolean(false) private val running = AtomicBoolean(false)
private var thread: Thread? = null
fun start() { fun start() {
running.set(true) running.set(true)
val thread = Thread { val thread = Thread {
plugin.slF4JLogger.info("Buffer Flusher Started") plugin.slF4JLogger.info("Buffer Flusher Started")
while (running.get()) { while (running.get()) {
try { flush()
transaction(plugin.db) {
val count = buffer.flush(this)
if (count > 0) {
plugin.slF4JLogger.info("Flushed $count Events")
}
}
} catch (e: Exception) {
plugin.slF4JLogger.warn("Failed to flush buffer.", e)
}
Thread.sleep(5000) Thread.sleep(5000)
} }
plugin.slF4JLogger.info("Buffer Flusher Stopped") plugin.slF4JLogger.info("Buffer Flusher Stopped")
@ -29,9 +21,24 @@ class BufferFlushThread(val plugin: FoundationHeimdallPlugin, val buffer: EventB
thread.name = "Heimdall Buffer Flush" thread.name = "Heimdall Buffer Flush"
thread.isDaemon = false thread.isDaemon = false
thread.start() thread.start()
this.thread = thread
} }
fun stop() { fun stop() {
running.set(false) running.set(false)
thread?.join()
}
fun flush() {
try {
transaction(plugin.db) {
val count = buffer.flush(this)
if (count > 0) {
plugin.slF4JLogger.info("Flushed $count Events")
}
}
} catch (e: Exception) {
plugin.slF4JLogger.warn("Failed to flush buffer.", e)
}
} }
} }

View File

@ -15,7 +15,7 @@ class BlockPlace(
val location: Location, val location: Location,
val material: Material, val material: Material,
val timestamp: Instant = Instant.now() val timestamp: Instant = Instant.now()
) : HeimdallEvent() { ) : HeimdallEvent() {
constructor(event: BlockPlaceEvent) : this(event.player.uniqueId, event.block.location, event.block.type) constructor(event: BlockPlaceEvent) : this(event.player.uniqueId, event.block.location, event.block.type)
override fun store(transaction: Transaction) { override fun store(transaction: Transaction) {

View File

@ -0,0 +1,25 @@
package cloud.kubelet.foundation.heimdall.event
import cloud.kubelet.foundation.heimdall.table.PlayerSessionTable
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.insert
import java.time.Instant
import java.util.*
class PlayerSession(
val playerUniqueIdentity: UUID,
val playerName: String,
val startTimeInstant: Instant,
val endTimeInstant: Instant
) : HeimdallEvent() {
override fun store(transaction: Transaction) {
transaction.apply {
PlayerSessionTable.insert {
it[player] = playerUniqueIdentity
it[name] = playerName
it[startTime] = startTimeInstant
it[endTime] = endTimeInstant
}
}
}
}

View File

@ -0,0 +1,11 @@
package cloud.kubelet.foundation.heimdall.table
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.javatime.timestamp
object PlayerSessionTable : Table("player_sessions") {
val player = uuid("player")
val name = text("name")
val startTime = timestamp("start")
val endTime = timestamp("end")
}

View File

@ -44,3 +44,13 @@ create table if not exists heimdall.block_places (
); );
-- --
select create_hypertable('heimdall.block_places', 'time', 'player', 4, if_not_exists => TRUE); select create_hypertable('heimdall.block_places', 'time', 'player', 4, if_not_exists => TRUE);
--
create table if not exists heimdall.player_sessions (
player uuid not null,
name text not null,
"start" timestamp not null,
"end" timestamp not null,
PRIMARY KEY (player, start)
);
--
select create_hypertable('heimdall.player_sessions', 'start', 'player', 4, if_not_exists => TRUE);