#!/usr/bin/env bun /** * Script to get all active connections to Dragonfly/Redis and group them by client name * * This script connects to the actual Dragonfly Docker container and retrieves: * - All active client connections * - Groups them by client name/type (removes timestamp suffixes) * - Shows connection details like IP, idle time, commands executed * * Usage: * bun run scripts/get-redis-connections.ts * * Requirements: * - Dragonfly Docker container must be running * - Script assumes default connection settings (localhost:6379) */ import Redis from 'ioredis'; interface ClientInfo { id: string; name?: string; addr: string; fd: string; age: string; idle: string; flags: string; db: string; sub: string; psub: string; multi: string; qbuf: string; qbufFree: string; obl: string; oll: string; omem: string; events: string; cmd: string; argv?: string; tot?: string; [key: string]: string | undefined; } interface GroupedConnections { [clientName: string]: { count: number; connections: ClientInfo[]; totalCommands: number; avgIdleTime: number; }; } class DragonFlyConnectionMonitor { private redis: Redis; constructor() { // Connect to Dragonfly using the same settings as the Docker setup this.redis = new Redis({ host: process.env.DRAGONFLY_HOST || 'localhost', port: parseInt(process.env.DRAGONFLY_PORT || '6379'), password: process.env.DRAGONFLY_PASSWORD || undefined, db: parseInt(process.env.DRAGONFLY_DATABASE || '0'), maxRetriesPerRequest: 3, lazyConnect: false, }); } /** * Parse CLIENT LIST output into structured data */ private parseClientList(clientListOutput: string): ClientInfo[] { const lines = clientListOutput.trim().split('\n'); const clients: ClientInfo[] = []; for (const line of lines) { if (!line.trim()) continue; const client: ClientInfo = { id: '', addr: '', fd: '', age: '', idle: '', flags: '', db: '', sub: '', psub: '', multi: '', qbuf: '', qbufFree: '', obl: '', oll: '', omem: '', events: '', cmd: '', }; // Parse key=value pairs const pairs = line.split(' '); for (const pair of pairs) { const [key, value] = pair.split('='); if (key && value !== undefined) { client[key] = value; } } clients.push(client); } return clients; } /** * Clean up client names by removing timestamp/number suffixes */ private cleanClientName(rawName: string): string { if (!rawName || rawName === 'unnamed') { return rawName; } // Remove timestamp/number suffixes (like BATCH-PROCESSOR-1749573709724) // Pattern: Remove dash followed by numbers at the end let cleanName = rawName.replace(/-\d+$/, ''); // Also handle patterns like NAME---1-1- (RedisInsight style) cleanName = cleanName.replace(/---\d+-\d+-?$/, ''); // Convert to a more readable format cleanName = cleanName .replace(/-/g, ' ') .toLowerCase() .replace(/\b\w/g, l => l.toUpperCase()); return cleanName; } /** * Group connections by client name/type */ private groupConnectionsByName(clients: ClientInfo[]): GroupedConnections { const grouped: GroupedConnections = {}; for (const client of clients) { // Determine client name - use 'name' field if set, otherwise infer from other properties let clientName = client.name || 'unnamed'; // Clean up the client name by removing timestamps/numbers if (clientName && clientName !== 'unnamed') { clientName = this.cleanClientName(clientName); } // If no name is set, try to infer from the connection characteristics if (clientName === 'unnamed' || !clientName) { if (client.flags.includes('M')) { clientName = 'Master Connection'; } else if (client.flags.includes('S')) { clientName = 'Slave Connection'; } else if (client.flags.includes('P')) { clientName = 'PubSub Connection'; } else if (client.cmd === 'monitor') { clientName = 'Monitor Connection'; } else if (client.addr.includes('127.0.0.1') || client.addr.includes('localhost')) { clientName = 'Local Client'; } else if (client.addr.includes('172.') || client.addr.includes('192.168.')) { clientName = 'Docker Network Client'; } else { clientName = `Client ${client.addr.split(':')[0]}`; } } if (!grouped[clientName]) { grouped[clientName] = { count: 0, connections: [], totalCommands: 0, avgIdleTime: 0, }; } grouped[clientName].count++; grouped[clientName].connections.push(client); // Parse command count if available const cmdCount = client.tot ? parseInt(client.tot) : 0; grouped[clientName].totalCommands += cmdCount; } // Calculate average idle times for (const groupName in grouped) { const group = grouped[groupName]; const totalIdle = group.connections.reduce((sum, client) => { return sum + parseInt(client.idle || '0'); }, 0); group.avgIdleTime = group.count > 0 ? Math.round(totalIdle / group.count) : 0; } return grouped; } /** * Format connection details for display */ private formatConnectionDetails(client: ClientInfo): string { const details = [ `ID: ${client.id}`, `Address: ${client.addr}`, `Age: ${client.age}s`, `Idle: ${client.idle}s`, `DB: ${client.db}`, `Flags: ${client.flags}`, `Last Command: ${client.cmd || 'none'}`, ]; if (client.tot) { details.push(`Total Commands: ${client.tot}`); } if (client.name) { details.unshift(`Original Name: ${client.name}`); } return details.join(', '); } /** * Get and display all connections grouped by name */ async getConnectionsGroupedByName(): Promise { try { console.log('šŸ” Connecting to Dragonfly...'); await this.redis.connect(); console.log('šŸ“” Fetching client connections...'); const clientListOutput = await this.redis.client('LIST') as string; if (!clientListOutput) { console.log('āŒ No client information available'); return; } const clients = this.parseClientList(clientListOutput); const grouped = this.groupConnectionsByName(clients); console.log('\nšŸŽÆ DRAGONFLY CONNECTION SUMMARY'); console.log('═'.repeat(60)); console.log(`Total Active Connections: ${clients.length}`); console.log(`Connection Groups: ${Object.keys(grouped).length}`); console.log(''); // Sort groups by count (most connections first) const sortedGroups = Object.entries(grouped).sort((a, b) => b[1].count - a[1].count); // Display grouped connections for (const [groupName, group] of sortedGroups) { console.log(`šŸ“‚ ${groupName.toUpperCase()}`); console.log(` Count: ${group.count}`); console.log(` Total Commands Executed: ${group.totalCommands}`); console.log(` Average Idle Time: ${group.avgIdleTime}s`); console.log(' Connections:'); for (const client of group.connections) { console.log(` ā”œā”€ ${this.formatConnectionDetails(client)}`); } console.log(''); } // Get additional server info console.log('šŸ–„ļø DRAGONFLY SERVER INFO'); console.log('═'.repeat(60)); const info = await this.redis.info('server'); const infoLines = info.split('\n'); for (const line of infoLines) { if (line.includes('dragonfly_version') || line.includes('connected_clients') || line.includes('used_memory_human') || line.includes('uptime_in_seconds')) { console.log(` ${line}`); } } // Get memory info console.log('\nšŸ’¾ MEMORY USAGE'); console.log('═'.repeat(60)); const memoryInfo = await this.redis.info('memory'); const memoryLines = memoryInfo.split('\n'); for (const line of memoryLines) { if (line.includes('used_memory_human') || line.includes('maxmemory_human') || line.includes('used_memory_percentage')) { console.log(` ${line}`); } } } catch (error) { console.error('āŒ Error connecting to Dragonfly:', error); console.log('\nšŸ’” Troubleshooting:'); console.log(' - Make sure Dragonfly Docker container is running'); console.log(' - Check if port 6379 is accessible'); console.log(' - Verify environment variables if using custom connection settings'); console.log(' - Run: docker ps | grep dragonfly'); } finally { await this.redis.disconnect(); } } /** * Monitor connections in real-time */ async monitorConnections(intervalSeconds: number = 5): Promise { console.log(`šŸ”„ Starting real-time monitoring (refresh every ${intervalSeconds}s)`); console.log('Press Ctrl+C to stop...\n'); const monitor = async () => { console.clear(); console.log(`šŸ• Last updated: ${new Date().toLocaleTimeString()}\n`); await this.getConnectionsGroupedByName(); }; // Initial run await monitor(); // Set up interval const interval = setInterval(monitor, intervalSeconds * 1000); // Handle graceful shutdown process.on('SIGINT', () => { clearInterval(interval); console.log('\nšŸ‘‹ Monitoring stopped'); process.exit(0); }); } } // Main execution async function main() { const args = process.argv.slice(2); const monitor = new DragonFlyConnectionMonitor(); if (args.includes('--monitor') || args.includes('-m')) { const intervalArg = args.find(arg => arg.startsWith('--interval=')); const interval = intervalArg ? parseInt(intervalArg.split('=')[1]) : 5; await monitor.monitorConnections(interval); } else { await monitor.getConnectionsGroupedByName(); } } // Help text if (process.argv.includes('--help') || process.argv.includes('-h')) { console.log(` šŸ‰ Dragonfly Connection Monitor Usage: bun run scripts/get-redis-connections.ts [options] Options: --help, -h Show this help message --monitor, -m Monitor connections in real-time --interval=N Set monitoring refresh interval in seconds (default: 5) Examples: bun run scripts/get-redis-connections.ts # One-time connection list bun run scripts/get-redis-connections.ts --monitor # Real-time monitoring bun run scripts/get-redis-connections.ts -m --interval=10 # Monitor every 10 seconds Environment Variables: DRAGONFLY_HOST Dragonfly host (default: localhost) DRAGONFLY_PORT Dragonfly port (default: 6379) DRAGONFLY_PASSWORD Dragonfly password (if auth enabled) DRAGONFLY_DATABASE Database number (default: 0) Features: ✨ Groups connections by clean names (removes timestamp suffixes) šŸ“Š Shows connection statistics and command counts šŸ”„ Real-time monitoring mode available šŸŽØ Color-coded output with connection details `); process.exit(0); } // Run the script console.log('šŸš€ Starting Redis connection monitor...'); main().catch((error) => { console.error('āŒ Script error:', error); process.exit(1); });