Optimized API update logic

This commit is contained in:
2025-07-19 23:30:27 -07:00
parent e8712a4a3d
commit 5f60e55e93
3 changed files with 723 additions and 34 deletions

View File

@@ -57,8 +57,8 @@ logger.info("Initialized logging system with file and console output")
# CONFIGURATION SETUP
# ==============================================
generate_config.generate_config()
logger.debug("Gennerated config.ini file using values from .env")
# generate_config.generate_config()
# logger.debug("Gennerated config.ini file using values from .env")
config = configparser.ConfigParser()
config.read('config.ini')
@@ -138,7 +138,7 @@ try:
DISCORD_TOKEN = config.get('Discord', 'Token')
ALLOWED_GUILD_ID = config.getint('Discord', 'AllowedGuildID')
REQUIRED_ROLE = "Game Server User"
UPDATE_INTERVAL = 1
UPDATE_INTERVAL = 10
EMBED_LOCATIONS_FILE = "embed_locations.json"
logger.debug("Loaded and validated configuration values from config.ini")
@@ -538,12 +538,15 @@ class PterodactylBot(commands.Bot):
Sets up caches, locks, and storage paths.
"""
super().__init__(*args, **kwargs)
self.pterodactyl_api = None # Will be initialized in setup_hook
self.server_cache: Dict[str, dict] = {} # Cache of server data
self.embed_locations: Dict[str, Dict[str, int]] = {} # Tracks embed positions
self.pterodactyl_api = None # Pterodactyl API client
self.server_cache: Dict[str, dict] = {} # Cache of server data from Pterodactyl
self.embed_locations: Dict[str, Dict[str, int]] = {} # Tracks where embeds are posted
self.update_lock = asyncio.Lock() # Prevents concurrent updates
self.embed_storage_path = Path(EMBED_LOCATIONS_FILE) # Storage file path
logger.info("Initialized PterodactylBot instance")
self.embed_storage_path = Path(EMBED_LOCATIONS_FILE) # File to store embed locations
# Track previous server states and CPU usage to detect changes
# Format: {server_id: (state, cpu_usage)}
self.previous_states: Dict[str, Tuple[str, float]] = {}
logger.info("Initialized PterodactylBot instance with state tracking")
async def setup_hook(self):
"""
@@ -773,28 +776,39 @@ class PterodactylBot(commands.Bot):
logger.debug(f"Successfully built status components for {name}")
return embed, view
@tasks.loop(minutes=UPDATE_INTERVAL)
@tasks.loop(seconds=UPDATE_INTERVAL)
async def update_status(self):
"""Background task to update all server status embeds periodically."""
logger.info("Starting scheduled server status update cycle")
async with self.update_lock:
"""
Background task to update server status embeds when:
1. Server power state changes (started/stopped/restarted)
2. Significant CPU usage change (>50% difference)
3. First time seeing the server
This minimizes API calls to Discord and updates while maintaining
real-time awareness of important server changes.
"""
logger.info("Starting optimized server status update cycle")
async with self.update_lock: # Ensure only one update runs at a time
try:
# Get current server list
# Fetch current server list from Pterodactyl
servers = await self.pterodactyl_api.get_servers()
if not servers:
logger.warning("No servers found in Pterodactyl panel during update")
return
# Update cache
# Update our local cache with fresh server data
self.server_cache = {server['attributes']['identifier']: server for server in servers}
logger.debug(f"Updated server cache with {len(servers)} servers")
# Update each server's embed
update_count = 0
error_count = 0
missing_count = 0
# Variables to track our update statistics
update_count = 0 # Successful updates
error_count = 0 # Failed updates
missing_count = 0 # Missing embeds
skipped_count = 0 # Servers that didn't need updates
# Process each server we're tracking embeds for
for server_id, location in list(self.embed_locations.items()):
# Skip if server no longer exists in Pterodactyl
if server_id not in self.server_cache:
logger.warning(f"Server {server_id} not found in cache, skipping update")
continue
@@ -803,38 +817,84 @@ class PterodactylBot(commands.Bot):
server_name = server_data['attributes']['name']
try:
logger.debug(f"Updating status for server {server_name} ({server_id})")
logger.debug(f"Checking status for server {server_name} ({server_id})")
# Get current server resource usage
resources = await self.pterodactyl_api.get_server_resources(server_id)
embed, view = await self.get_server_status_embed(server_data, resources)
channel = self.get_channel(int(location['channel_id']))
if not channel:
logger.warning(f"Channel {location['channel_id']} not found for server {server_id}")
continue
current_state = resources.get('attributes', {}).get('current_state', 'offline')
cpu_usage = round(resources.get('attributes', {}).get('resources', {}).get('cpu_absolute', 0), 2)
# Retrieve previous recorded state and CPU usage
prev_state, prev_cpu = self.previous_states.get(server_id, (None, 0))
# DECISION LOGIC: Should we update the embed?
needs_update = False
# 1. Check if power state changed (most important)
if current_state != prev_state:
logger.debug(f"Power state changed for {server_name}: {prev_state} -> {current_state}")
needs_update = True
# 2. Check for significant CPU change (only if server is running)
elif current_state == 'running' and abs(cpu_usage - prev_cpu) > 50:
logger.debug(f"Significant CPU change for {server_name}: {prev_cpu}% -> {cpu_usage}%")
needs_update = True
# 3. First time we're seeing this server (initial update)
elif prev_state is None:
logger.debug(f"First check for {server_name}, performing initial update")
needs_update = True
# PERFORM UPDATE IF NEEDED
if needs_update:
# Generate fresh embed and view components
embed, view = await self.get_server_status_embed(server_data, resources)
message = await channel.fetch_message(int(location['message_id']))
await message.edit(embed=embed, view=view)
update_count += 1
logger.debug(f"Successfully updated status for {server_name}")
# Get the channel where this server's embed lives
channel = self.get_channel(int(location['channel_id']))
if not channel:
logger.warning(f"Channel {location['channel_id']} not found for server {server_id}")
continue
# Fetch and update the existing message
message = await channel.fetch_message(int(location['message_id']))
await message.edit(embed=embed, view=view)
update_count += 1
logger.debug(f"Updated status for {server_name}")
# Update our state tracking with new values
self.previous_states[server_id] = (current_state, cpu_usage)
else:
# No significant changes detected
skipped_count += 1
logger.debug(f"No changes detected for {server_name}, skipping update")
except discord.NotFound:
# Embed message was deleted - clean up our tracking
logger.warning(f"Embed for server {server_id} not found, removing from tracking")
self.embed_locations.pop(server_id, None)
self.previous_states.pop(server_id, None) # Also clean up state tracking
missing_count += 1
await self.save_embed_locations()
except Exception as e:
logger.error(f"Failed to update status for server {server_id}: {str(e)}")
error_count += 1
await asyncio.sleep(1) # Delay between updates
# Small delay between servers to avoid rate limits
await asyncio.sleep(0.5)
# Log summary of this update cycle
logger.info(
f"Status update complete: {update_count} updated, "
f"{missing_count} missing, {error_count} errors"
f"Update cycle complete: "
f"{update_count} updated, "
f"{skipped_count} skipped, "
f"{missing_count} missing, "
f"{error_count} errors"
)
except Exception as e:
logger.error(f"Error in update_status task: {str(e)}")
# If something went wrong, wait before retrying
await asyncio.sleep(5)
@update_status.before_loop