MosswartOverlord/db_async.py
2025-05-23 08:11:11 +00:00

112 lines
No EOL
4.3 KiB
Python

import os
import sqlalchemy
from databases import Database
from sqlalchemy import MetaData, Table, Column, Integer, String, Float, DateTime, text
# Environment: Postgres/TimescaleDB connection URL
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:password@localhost:5432/dereth")
# Async database client
database = Database(DATABASE_URL)
# Metadata for SQLAlchemy Core
metadata = MetaData()
# Telemetry events hypertable schema
telemetry_events = Table(
"telemetry_events",
metadata,
Column("id", Integer, primary_key=True),
Column("character_name", String, nullable=False),
Column("char_tag", String, nullable=True),
Column("session_id", String, nullable=False, index=True),
Column("timestamp", DateTime(timezone=True), nullable=False, index=True),
Column("ew", Float, nullable=False),
Column("ns", Float, nullable=False),
Column("z", Float, nullable=False),
Column("kills", Integer, nullable=False),
Column("kills_per_hour", Float, nullable=True),
Column("onlinetime", String, nullable=True),
Column("deaths", Integer, nullable=False),
Column("rares_found", Integer, nullable=False),
Column("prismatic_taper_count", Integer, nullable=False),
Column("vt_state", String, nullable=True),
# New telemetry metrics
Column("mem_mb", Float, nullable=True),
Column("cpu_pct", Float, nullable=True),
Column("mem_handles", Integer, nullable=True),
Column("latency_ms", Float, nullable=True),
)
# Persistent kill statistics per character
# Persistent kill statistics per character
char_stats = Table(
"char_stats",
metadata,
Column("character_name", String, primary_key=True),
Column("total_kills", Integer, nullable=False, default=0),
)
# Rare event tracking: total and per-session counts
rare_stats = Table(
"rare_stats",
metadata,
Column("character_name", String, primary_key=True),
Column("total_rares", Integer, nullable=False, default=0),
)
rare_stats_sessions = Table(
"rare_stats_sessions",
metadata,
Column("character_name", String, primary_key=True),
Column("session_id", String, primary_key=True),
Column("session_rares", Integer, nullable=False, default=0),
)
# Spawn events: record mob spawns for heatmapping
spawn_events = Table(
"spawn_events",
metadata,
Column("id", Integer, primary_key=True),
Column("character_name", String, nullable=False),
Column("mob", String, nullable=False),
Column("timestamp", DateTime(timezone=True), nullable=False, index=True),
Column("ew", Float, nullable=False),
Column("ns", Float, nullable=False),
Column("z", Float, nullable=False),
)
# Rare events: record individual rare spawns for future heatmaps
rare_events = Table(
"rare_events",
metadata,
Column("id", Integer, primary_key=True),
Column("character_name", String, nullable=False),
Column("name", String, nullable=False),
Column("timestamp", DateTime(timezone=True), nullable=False, index=True),
Column("ew", Float, nullable=False),
Column("ns", Float, nullable=False),
Column("z", Float, nullable=False),
)
async def init_db_async():
"""Create tables and enable TimescaleDB hypertable for telemetry_events."""
# Create tables in Postgres
engine = sqlalchemy.create_engine(DATABASE_URL)
metadata.create_all(engine)
# Enable TimescaleDB extension and convert telemetry_events to hypertable
# Use a transactional context to ensure DDL statements are committed
with engine.begin() as conn:
# Enable or update TimescaleDB extension
try:
conn.execute(text("CREATE EXTENSION IF NOT EXISTS timescaledb"))
except Exception as e:
print(f"Warning: failed to create extension timescaledb: {e}")
try:
conn.execute(text("ALTER EXTENSION timescaledb UPDATE"))
except Exception as e:
print(f"Warning: failed to update timescaledb extension: {e}")
# Create hypertable for telemetry_events, skip default indexes to avoid collisions
try:
conn.execute(text(
"SELECT create_hypertable('telemetry_events', 'timestamp', \
if_not_exists => true, create_default_indexes => false)"
))
except Exception as e:
print(f"Warning: failed to create hypertable telemetry_events: {e}")