33import logging
44import time
55import json
6+ import psutil
7+ import os
68from datetime import datetime
79from discord .ext import commands , tasks
810from typing import Dict , List , Optional
@@ -28,6 +30,7 @@ def __init__(self, bot):
2830
2931 # Start background tasks
3032 self .send_stats_task .start ()
33+ self .send_performance_update_task .start ()
3134 self .reset_daily_stats_task .start ()
3235 self .load_stats_task .start ()
3336
@@ -36,6 +39,7 @@ def __init__(self, bot):
3639 def cog_unload (self ):
3740 """Clean up when cog is unloaded"""
3841 self .send_stats_task .cancel ()
42+ self .send_performance_update_task .cancel ()
3943 self .reset_daily_stats_task .cancel ()
4044 self .load_stats_task .cancel ()
4145 # Save stats before unloading
@@ -72,7 +76,12 @@ async def load_stats_from_mongodb(self):
7276 self .daily_commands = stats_doc .get ("daily_commands" , 0 )
7377 self .command_types = stats_doc .get ("command_types" , {})
7478 self .last_stats_update = stats_doc .get ("last_update" , 0 )
75- logging .info (f"Loaded stats from MongoDB: { self .command_count } commands" )
79+
80+ # Load uptime start if available, otherwise use current time
81+ if "uptime_start" in stats_doc :
82+ self .start_time = datetime .fromtimestamp (stats_doc ["uptime_start" ])
83+
84+ logging .info (f"Loaded stats from MongoDB: { self .command_count } commands, { len (self .command_types )} unique commands" )
7685 else :
7786 logging .info ("No stats found in MongoDB, using default values" )
7887 except Exception as e :
@@ -85,13 +94,47 @@ async def save_stats_to_mongodb(self):
8594 return False
8695
8796 try :
88- # Prepare stats document
97+ # Get system performance metrics
98+ try :
99+ process = psutil .Process (os .getpid ())
100+ memory_usage = process .memory_info ().rss / 1024 / 1024 # MB
101+ cpu_usage = process .cpu_percent ()
102+ except (psutil .NoSuchProcess , psutil .AccessDenied ):
103+ memory_usage = 0
104+ cpu_usage = 0
105+
106+ # Calculate total user count
107+ total_users = sum (guild .member_count or 0 for guild in self .bot .guilds )
108+
109+ # Prepare comprehensive stats document
89110 stats_doc = {
90111 "command_count" : self .command_count ,
91112 "daily_commands" : self .daily_commands ,
92113 "command_types" : self .command_types ,
93114 "last_update" : time .time (),
94- "updated_at" : datetime .now ()
115+ "updated_at" : datetime .now (),
116+
117+ # Performance metrics
118+ "user_count" : total_users ,
119+ "latency" : round (self .bot .latency * 1000 , 2 ),
120+ "shard_count" : self .bot .shard_count or 1 ,
121+ "memory_usage" : round (memory_usage , 2 ),
122+ "cpu_usage" : round (cpu_usage , 2 ),
123+ "cached_users" : len (self .bot .users ),
124+
125+ # Guild information
126+ "guild_count" : len (self .bot .guilds ),
127+ "guild_list" : [str (guild .id ) for guild in self .bot .guilds ],
128+
129+ # Uptime information
130+ "uptime_start" : self .start_time .timestamp (),
131+ "uptime_seconds" : (datetime .now () - self .start_time ).total_seconds (),
132+
133+ # System information
134+ "python_version" : f"{ os .sys .version_info .major } .{ os .sys .version_info .minor } .{ os .sys .version_info .micro } " ,
135+ "discord_py_version" : discord .__version__ ,
136+ "platform" : os .name ,
137+ "process_id" : os .getpid ()
95138 }
96139
97140 # Update or insert the global stats document
@@ -127,6 +170,54 @@ async def before_send_stats_task(self):
127170 """Wait until the bot is ready before starting the stats update loop"""
128171 await self .bot .wait_until_ready ()
129172
173+ @tasks .loop (minutes = 1 )
174+ async def send_performance_update_task (self ):
175+ """Send performance metrics update every minute for real-time monitoring"""
176+ try :
177+ await self .send_performance_update ()
178+ except Exception as e :
179+ logging .debug (f"Error in performance update task: { e } " )
180+
181+ @send_performance_update_task .before_loop
182+ async def before_send_performance_update_task (self ):
183+ """Wait until the bot is ready before starting the performance update loop"""
184+ await self .bot .wait_until_ready ()
185+
186+ async def send_performance_update (self ):
187+ """Send lightweight performance update to dashboard"""
188+ try :
189+ process = psutil .Process (os .getpid ())
190+ memory_usage = process .memory_info ().rss / 1024 / 1024 # MB
191+ cpu_usage = process .cpu_percent ()
192+ except (psutil .NoSuchProcess , psutil .AccessDenied ):
193+ memory_usage = 0
194+ cpu_usage = 0
195+
196+ performance_data = {
197+ "type" : "performance_update" ,
198+ "latency" : round (self .bot .latency * 1000 , 2 ),
199+ "memory_usage" : round (memory_usage , 2 ),
200+ "cpu_usage" : round (cpu_usage , 2 ),
201+ "guild_count" : len (self .bot .guilds ),
202+ "user_count" : sum (guild .member_count or 0 for guild in self .bot .guilds ),
203+ "cached_users" : len (self .bot .users ),
204+ "timestamp" : time .time ()
205+ }
206+
207+ try :
208+ async with aiohttp .ClientSession () as session :
209+ async with session .post (
210+ f"{ self .dashboard_url } /api/stats/performance" ,
211+ json = performance_data ,
212+ timeout = 5
213+ ) as response :
214+ if response .status == 200 :
215+ logging .debug ("✅ Performance update sent successfully" )
216+ else :
217+ logging .debug (f"❌ Failed to send performance update: { response .status } " )
218+ except Exception as e :
219+ logging .debug (f"❌ Error sending performance update: { e } " )
220+
130221 @tasks .loop (hours = 24 )
131222 async def reset_daily_stats_task (self ):
132223 """Reset daily command count at midnight"""
@@ -146,6 +237,40 @@ async def send_comprehensive_stats(self):
146237 """Send comprehensive stats to dashboard"""
147238 uptime = datetime .now () - self .start_time
148239
240+ # Get system performance metrics
241+ try :
242+ process = psutil .Process (os .getpid ())
243+ memory_usage = process .memory_info ().rss / 1024 / 1024 # MB
244+ cpu_usage = process .cpu_percent ()
245+ except (psutil .NoSuchProcess , psutil .AccessDenied ):
246+ memory_usage = 0
247+ cpu_usage = 0
248+
249+ # Calculate total user count across all guilds
250+ total_users = sum (guild .member_count or 0 for guild in self .bot .guilds )
251+
252+ # Get detailed guild information
253+ guild_details = []
254+ for guild in self .bot .guilds :
255+ try :
256+ guild_details .append ({
257+ "id" : str (guild .id ),
258+ "name" : guild .name ,
259+ "member_count" : guild .member_count or 0 ,
260+ "owner_id" : str (guild .owner_id ) if guild .owner_id else None ,
261+ "created_at" : guild .created_at .isoformat () if guild .created_at else None ,
262+ "features" : list (guild .features ) if guild .features else [],
263+ "verification_level" : str (guild .verification_level ) if guild .verification_level else "none" ,
264+ "premium_tier" : guild .premium_tier if hasattr (guild , 'premium_tier' ) else 0
265+ })
266+ except Exception as e :
267+ logging .warning (f"Error getting details for guild { guild .id } : { e } " )
268+ guild_details .append ({
269+ "id" : str (guild .id ),
270+ "name" : getattr (guild , 'name' , 'Unknown' ),
271+ "member_count" : getattr (guild , 'member_count' , 0 ) or 0
272+ })
273+
149274 stats = {
150275 "uptime" : {
151276 "days" : uptime .days ,
@@ -157,23 +282,27 @@ async def send_comprehensive_stats(self):
157282 "guilds" : {
158283 "count" : len (self .bot .guilds ),
159284 "list" : [str (guild .id ) for guild in self .bot .guilds ],
160- "detailed" : [
161- {
162- "id" : str (guild .id ),
163- "name" : guild .name ,
164- "member_count" : guild .member_count or 0
165- } for guild in self .bot .guilds
166- ]
285+ "detailed" : guild_details
167286 },
168287 "performance" : {
169- "user_count" : sum (guild .member_count or 0 for guild in self .bot .guilds ),
170- "latency" : round (self .bot .latency * 1000 , 2 ),
171- "shard_count" : self .bot .shard_count or 1
288+ "user_count" : total_users ,
289+ "latency" : round (self .bot .latency * 1000 , 2 ), # Convert to milliseconds
290+ "shard_count" : self .bot .shard_count or 1 ,
291+ "memory_usage" : round (memory_usage , 2 ),
292+ "cpu_usage" : round (cpu_usage , 2 ),
293+ "cached_users" : len (self .bot .users ),
294+ "cached_messages" : getattr (self .bot , '_connection' , {}).get ('_messages' , {}) and len (getattr (self .bot ._connection , '_messages' , {})) or 0
172295 },
173296 "commands" : {
174297 "total_executed" : self .command_count ,
175298 "daily_count" : self .daily_commands ,
176299 "command_types" : self .command_types .copy ()
300+ },
301+ "system" : {
302+ "python_version" : f"{ os .sys .version_info .major } .{ os .sys .version_info .minor } .{ os .sys .version_info .micro } " ,
303+ "discord_py_version" : discord .__version__ ,
304+ "platform" : os .name ,
305+ "process_id" : os .getpid ()
177306 }
178307 }
179308
@@ -187,8 +316,12 @@ async def send_comprehensive_stats(self):
187316 if response .status == 200 :
188317 logging .debug ("✅ Comprehensive stats sent to dashboard successfully" )
189318 self .last_stats_update = time .time ()
319+
320+ # Also save to MongoDB after successful dashboard update
321+ await self .save_stats_to_mongodb ()
190322 else :
191- logging .warning (f"❌ Failed to send comprehensive stats: { response .status } " )
323+ response_text = await response .text ()
324+ logging .warning (f"❌ Failed to send comprehensive stats: { response .status } - { response_text } " )
192325 except Exception as e :
193326 logging .error (f"❌ Error sending comprehensive stats: { e } " )
194327
@@ -274,6 +407,17 @@ async def stats_status(self, ctx):
274407 uptime = datetime .now () - self .start_time
275408 last_update_ago = time .time () - self .last_stats_update if self .last_stats_update else None
276409
410+ # Get system metrics
411+ try :
412+ process = psutil .Process (os .getpid ())
413+ memory_usage = process .memory_info ().rss / 1024 / 1024 # MB
414+ cpu_usage = process .cpu_percent ()
415+ except (psutil .NoSuchProcess , psutil .AccessDenied ):
416+ memory_usage = 0
417+ cpu_usage = 0
418+
419+ total_users = sum (guild .member_count or 0 for guild in self .bot .guilds )
420+
277421 embed = discord .Embed (
278422 title = "📊 Stats Cog Status" ,
279423 color = discord .Color .blue ()
@@ -287,6 +431,22 @@ async def stats_status(self, ctx):
287431 inline = True
288432 )
289433
434+ embed .add_field (
435+ name = "🌐 Bot Performance" ,
436+ value = f"**Guilds:** { len (self .bot .guilds ):,} \n "
437+ f"**Users:** { total_users :,} \n "
438+ f"**Latency:** { round (self .bot .latency * 1000 , 2 )} ms" ,
439+ inline = True
440+ )
441+
442+ embed .add_field (
443+ name = "💻 System Performance" ,
444+ value = f"**Memory Usage:** { memory_usage :.1f} MB\n "
445+ f"**CPU Usage:** { cpu_usage :.1f} %\n "
446+ f"**Shards:** { self .bot .shard_count or 1 } " ,
447+ inline = True
448+ )
449+
290450 embed .add_field (
291451 name = "⏱️ Timing" ,
292452 value = f"**Uptime:** { uptime .days } d { uptime .seconds // 3600 } h { (uptime .seconds % 3600 )// 60 } m\n "
@@ -300,7 +460,15 @@ async def stats_status(self, ctx):
300460 f"**Update Interval:** 5 minutes\n "
301461 f"**MongoDB Storage:** Enabled\n "
302462 f"**Tasks Running:** { self .send_stats_task .is_running ()} " ,
303- inline = False
463+ inline = True
464+ )
465+
466+ embed .add_field (
467+ name = "📋 System Info" ,
468+ value = f"**Python:** { os .sys .version_info .major } .{ os .sys .version_info .minor } .{ os .sys .version_info .micro } \n "
469+ f"**Discord.py:** { discord .__version__ } \n "
470+ f"**Platform:** { os .name } " ,
471+ inline = True
304472 )
305473
306474 # Top 5 commands
@@ -323,8 +491,10 @@ async def force_stats_update(self, ctx):
323491 await ctx .send ("🔄 Sending stats update..." )
324492
325493 try :
326- # Update dashboard
494+ # Update dashboard with comprehensive stats
327495 await self .send_comprehensive_stats ()
496+ # Send performance update as well
497+ await self .send_performance_update ()
328498 # Save to MongoDB
329499 mongo_success = await self .save_stats_to_mongodb ()
330500
@@ -335,6 +505,18 @@ async def force_stats_update(self, ctx):
335505 except Exception as e :
336506 await ctx .send (f"❌ Failed to send stats update: { e } " )
337507
508+ @commands .command (name = 'testperformance' , aliases = ['perftest' ])
509+ @commands .is_owner ()
510+ async def test_performance_update (self , ctx ):
511+ """Test performance metrics update"""
512+ await ctx .send ("🔄 Testing performance update..." )
513+
514+ try :
515+ await self .send_performance_update ()
516+ await ctx .send ("✅ Performance update sent successfully!" )
517+ except Exception as e :
518+ await ctx .send (f"❌ Failed to send performance update: { e } " )
519+
338520 @commands .command (name = 'resetstats' )
339521 @commands .is_owner ()
340522 async def reset_stats (self , ctx ):
0 commit comments