UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

1,081 lines (1,028 loc) 64.3 kB
/** * LiveView Process Lifecycle Handler * * Tracks full Phoenix LiveView process lifecycle including spawns, exits, message passing, * ETS operations, and GenServer calls. Essential for debugging why LiveView processes die * during tests and identifying process communication issues. */ import { BaseToolHandler } from './base-handler.js'; export class LiveViewLifecycleHandler extends BaseToolHandler { tools = [ { name: 'track_liveview_lifecycle', description: `🔄 LIVEVIEW LIFECYCLE TRACKER: Track full Phoenix LiveView process lifecycle including spawns, exits, message passing, ETS operations, and GenServer calls. Essential for debugging why LiveView processes die during tests and identifying process communication issues. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, trackingDuration: { type: 'number', default: 30000, description: 'How long to track process lifecycle events (ms)' }, focusedProcessId: { type: 'string', description: 'Specific LiveView process ID to focus on (optional)' }, captureMessageQueue: { type: 'boolean', default: true, description: 'Capture message queue contents for processes' }, trackETSOperations: { type: 'boolean', default: true, description: 'Track ETS table operations that might affect state' }, monitorGenServerCalls: { type: 'boolean', default: true, description: 'Monitor GenServer calls and responses' }, captureAssignsChanges: { type: 'boolean', default: true, description: 'Track LiveView assigns changes over time' }, alertOnProcessDeath: { type: 'boolean', default: true, description: 'Immediate alerts when processes die unexpectedly' } }, required: ['sessionId'] } }, { name: 'analyze_process_death_causes', description: `💀 PROCESS DEATH ANALYZER: Analyze why LiveView processes die, identify common patterns, and suggest fixes. Provides detailed forensic analysis of process death scenarios with actionable recommendations. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, analysisWindow: { type: 'number', default: 60000, description: 'Time window to analyze for process deaths (ms)' }, includeStackTraces: { type: 'boolean', default: true, description: 'Include stack traces for crashed processes' }, correlateWithGraphQL: { type: 'boolean', default: true, description: 'Correlate process deaths with GraphQL request failures' }, trackMemoryLeaks: { type: 'boolean', default: true, description: 'Track memory usage patterns before process death' }, identifyCommonPatterns: { type: 'boolean', default: true, description: 'Identify common patterns in process death scenarios' } }, required: ['sessionId'] } }, { name: 'monitor_message_passing', description: `📨 MESSAGE PASSING MONITOR: Monitor inter-process message passing, identify stuck messages, and track communication patterns. Essential for debugging LiveView process communication issues and message queue problems. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, monitorDuration: { type: 'number', default: 20000, description: 'How long to monitor message passing (ms)' }, trackMessageQueue: { type: 'boolean', default: true, description: 'Track message queue sizes and contents' }, identifyStuckMessages: { type: 'boolean', default: true, description: 'Identify messages that never get processed' }, captureMessageContent: { type: 'boolean', default: true, description: 'Capture actual message content for analysis' }, trackProcessCommunication: { type: 'boolean', default: true, description: 'Track which processes communicate with each other' }, alertOnQueueBacklog: { type: 'boolean', default: true, description: 'Alert when message queues get too large' }, queueBacklogThreshold: { type: 'number', default: 50, description: 'Message queue size threshold for alerts' } }, required: ['sessionId'] } }, { name: 'inspect_ets_operations', description: `🗄️ ETS OPERATIONS INSPECTOR: Inspect ETS table operations, track state changes, and identify cross-process state issues. Critical for debugging mock state visibility and cross-process data access issues. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, inspectionDuration: { type: 'number', default: 15000, description: 'How long to inspect ETS operations (ms)' }, focusedTables: { type: 'array', items: { type: 'string' }, description: 'Specific ETS table names to focus on (optional)' }, trackTableAccess: { type: 'boolean', default: true, description: 'Track which processes access which ETS tables' }, captureStateChanges: { type: 'boolean', default: true, description: 'Capture before/after states for ETS operations' }, identifyAccessPatterns: { type: 'boolean', default: true, description: 'Identify patterns in ETS table access' }, trackMockState: { type: 'boolean', default: true, description: 'Specifically track mock-related ETS operations' }, alertOnAccessFailures: { type: 'boolean', default: true, description: 'Alert when processes fail to access ETS tables' } }, required: ['sessionId'] } } ]; async handle(toolName, args, sessions) { // Validate session exists const session = sessions.get(args.sessionId); if (!session) { return { content: [{ type: 'text', text: `❌ No active debug session found with ID: ${args.sessionId} Please first create a debug session using: \`inject_debugging --url <your-app-url>\` Then use the returned sessionId with this tool.` }] }; } switch (toolName) { case 'track_liveview_lifecycle': return this.trackLiveViewLifecycle(args, session); case 'analyze_process_death_causes': return this.analyzeProcessDeathCauses(args, session); case 'monitor_message_passing': return this.monitorMessagePassing(args, session); case 'inspect_ets_operations': return this.inspectETSOperations(args, session); default: throw new Error(`Unknown LiveView lifecycle tool: ${toolName}`); } } async trackLiveViewLifecycle(args, session) { const { trackingDuration = 30000, focusedProcessId, captureMessageQueue = true, trackETSOperations = true, monitorGenServerCalls = true, captureAssignsChanges = true, alertOnProcessDeath = true } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } const lifecycleEvents = []; const liveviewStates = new Map(); // Inject comprehensive lifecycle tracking await page.addInitScript((config) => { window.__liveviewLifecycle = { events: [], states: new Map(), config: config }; // Track WebSocket events for LiveView state changes const originalWebSocket = window.WebSocket; window.WebSocket = class extends originalWebSocket { constructor(url, protocols) { super(url, protocols); const socketId = Math.random().toString(36).substr(2, 9); this.addEventListener('open', () => { window.__liveviewLifecycle.events.push({ id: Math.random().toString(36).substr(2, 9), type: 'websocket_event', timestamp: Date.now(), details: { event: 'connection_opened', socketId, url: url.toString() } }); }); this.addEventListener('message', (event) => { try { const data = JSON.parse(event.data); window.__liveviewLifecycle.events.push({ id: Math.random().toString(36).substr(2, 9), type: 'message', timestamp: Date.now(), details: { event: 'liveview_message', socketId, messageType: data[0] || 'unknown', topic: data[1] || 'unknown', payload: config.captureAssignsChanges ? data[2] : '[payload hidden]' } }); // Track LiveView state changes if (data[0] === 'phx_reply' && data[2] && data[2].rendered) { const processInfo = { processId: data[1] || socketId, socketId, currentView: data[2].rendered.s ? data[2].rendered.s[0] : 'unknown', isAlive: true, lastActivity: Date.now(), assignsSnapshot: config.captureAssignsChanges ? data[2] : null }; window.__liveviewLifecycle.states.set(socketId, processInfo); } } catch (e) { // Handle non-JSON messages } }); this.addEventListener('close', (event) => { window.__liveviewLifecycle.events.push({ id: Math.random().toString(36).substr(2, 9), type: 'exit', timestamp: Date.now(), details: { event: 'connection_closed', socketId, code: event.code, reason: event.reason, wasClean: event.wasClean } }); // Mark process as dead const state = window.__liveviewLifecycle.states.get(socketId); if (state) { state.isAlive = false; window.__liveviewLifecycle.states.set(socketId, state); } }); this.addEventListener('error', (event) => { window.__liveviewLifecycle.events.push({ id: Math.random().toString(36).substr(2, 9), type: 'exit', timestamp: Date.now(), details: { event: 'connection_error', socketId, error: 'WebSocket error occurred' } }); }); } }; // Monitor DOM mutations that might indicate LiveView updates const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { // Look for LiveView-specific attributes mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { // Element node const element = node; if (element.hasAttribute && (element.hasAttribute('phx-click') || element.hasAttribute('phx-submit') || element.hasAttribute('phx-change') || element.getAttribute('data-phx-main'))) { window.__liveviewLifecycle.events.push({ id: Math.random().toString(36).substr(2, 9), type: 'spawn', timestamp: Date.now(), details: { event: 'liveview_dom_update', elementType: element.tagName, hasPhxAttributes: true, phxMain: element.getAttribute('data-phx-main') } }); } } }); } }); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-phx-main', 'phx-click', 'phx-submit', 'phx-change'] }); // Store observer for cleanup window.__liveviewObserver = observer; }, { captureMessageQueue, trackETSOperations, monitorGenServerCalls, captureAssignsChanges, alertOnProcessDeath, focusedProcessId }); // Monitor console for Phoenix-specific logs const consoleMessages = []; const consoleHandler = (msg) => { const text = msg.text(); if (text.includes('LiveView') || text.includes('Phoenix') || text.includes('phx_')) { consoleMessages.push({ timestamp: Date.now(), type: msg.type(), text: text, location: msg.location() }); } }; page.on('console', consoleHandler); // Track for the specified duration await new Promise(resolve => setTimeout(resolve, trackingDuration)); // Get lifecycle data from browser const lifecycleData = await page.evaluate(() => { // Clean up observer if (window.__liveviewObserver) { window.__liveviewObserver.disconnect(); } return { events: window.__liveviewLifecycle?.events || [], states: Array.from(window.__liveviewLifecycle?.states?.entries() || []) }; }); // Clean up console handler page.off('console', consoleHandler); // Process and analyze data const processDeaths = lifecycleData.events.filter((e) => e.type === 'exit' && e.details.event !== 'connection_closed'); const activeProcesses = lifecycleData.states.filter(([_, state]) => state.isAlive); return { content: [{ type: 'text', text: `## 🔄 LiveView Process Lifecycle Report ### Lifecycle Summary - **Tracking Duration**: ${trackingDuration}ms - **Total Events**: ${lifecycleData.events.length} - **Active Processes**: ${activeProcesses.length} - **Process Deaths**: ${processDeaths.length} - **Console Messages**: ${consoleMessages.length} ### Process States ${activeProcesses.length > 0 ? activeProcesses.map(([socketId, state]) => ` #### Process ${socketId} - **View**: ${state.currentView} - **Status**: ${state.isAlive ? '✅ Alive' : '💀 Dead'} - **Last Activity**: ${new Date(state.lastActivity).toISOString()} - **Socket ID**: ${state.socketId} ${captureAssignsChanges && state.assignsSnapshot ? `- **Recent Assigns**: ${JSON.stringify(state.assignsSnapshot, null, 2).slice(0, 200)}...` : ''} `).join('') : 'No active LiveView processes detected'} ### Process Deaths Analysis ${processDeaths.length > 0 ? processDeaths.map((death, i) => ` #### Death ${i + 1} - **Time**: ${new Date(death.timestamp).toISOString()} - **Event**: ${death.details.event} - **Reason**: ${death.details.reason || death.details.error || 'Unknown'} - **Code**: ${death.details.code || 'N/A'} - **Socket**: ${death.details.socketId} `).join('') : '✅ No process deaths detected during monitoring'} ### Recent Events Timeline ${lifecycleData.events.slice(-10).map((event, i) => ` ${i + 1}. **${event.type}** (${new Date(event.timestamp).toISOString()}) - Event: ${event.details.event} - Details: ${JSON.stringify(event.details, null, 2).slice(0, 150)}... `).join('')} ### Console Activity ${consoleMessages.length > 0 ? consoleMessages.slice(-5).map((msg, i) => ` ${i + 1}. **${msg.type}** - ${msg.text.slice(0, 100)}... - Time: ${new Date(msg.timestamp).toISOString()} `).join('') : 'No Phoenix/LiveView console messages detected'} ### Configuration - **Message Queue Capture**: ${captureMessageQueue ? '✅' : '❌'} - **ETS Operations**: ${trackETSOperations ? '✅' : '❌'} - **GenServer Calls**: ${monitorGenServerCalls ? '✅' : '❌'} - **Assigns Changes**: ${captureAssignsChanges ? '✅' : '❌'} - **Death Alerts**: ${alertOnProcessDeath ? '✅' : '❌'} ${focusedProcessId ? `- **Focused Process**: ${focusedProcessId}` : ''} ### Next Steps - Use \`analyze_process_death_causes\` for detailed death analysis - Use \`monitor_message_passing\` to debug communication issues - Use \`inspect_ets_operations\` for state management debugging - Check Phoenix logs for additional context on process lifecycles ### Backend Integration Note This tool provides browser-side LiveView lifecycle tracking. For complete server-side process monitoring, integrate with Phoenix telemetry events and BEAM VM process inspection tools.` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ LiveView Lifecycle Tracking Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'} ### Troubleshooting - Ensure your application uses Phoenix LiveView - Verify WebSocket connections are active during monitoring - Check that LiveView processes are spawning during the tracking period` }] }; } } async analyzeProcessDeathCauses(args, session) { const { analysisWindow = 60000, includeStackTraces = true, correlateWithGraphQL = true, trackMemoryLeaks = true, identifyCommonPatterns = true } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } // First run lifecycle tracking to get recent data const lifecycleResult = await this.trackLiveViewLifecycle({ sessionId: args.sessionId, trackingDuration: analysisWindow, alertOnProcessDeath: true, captureAssignsChanges: true }, session); // Analyze console for crash patterns const crashPatterns = []; // Monitor console for error patterns await page.addInitScript(() => { window.__deathAnalysis = { errors: [], memoryUsage: [], startTime: Date.now() }; // Override console.error to capture crashes const originalError = console.error; console.error = function (...args) { window.__deathAnalysis.errors.push({ timestamp: Date.now(), message: args.map(arg => String(arg)).join(' '), stack: new Error().stack }); return originalError.apply(console, args); }; // Track memory usage if available if ('memory' in performance) { setInterval(() => { window.__deathAnalysis.memoryUsage.push({ timestamp: Date.now(), used: performance.memory.usedJSHeapSize, total: performance.memory.totalJSHeapSize, limit: performance.memory.jsHeapSizeLimit }); }, 1000); } }); // Monitor for analysis window await new Promise(resolve => setTimeout(resolve, analysisWindow)); // Get analysis data const analysisData = await page.evaluate(() => { return window.__deathAnalysis || { errors: [], memoryUsage: [] }; }); // Identify patterns const errorPatterns = this.identifyErrorPatterns(analysisData.errors); const memoryPattern = this.analyzeMemoryPattern(analysisData.memoryUsage); return { content: [{ type: 'text', text: `## 💀 Process Death Causes Analysis ### Analysis Summary - **Analysis Window**: ${analysisWindow}ms - **Errors Captured**: ${analysisData.errors.length} - **Memory Samples**: ${analysisData.memoryUsage.length} - **Common Patterns**: ${errorPatterns.length} ### Error Patterns ${errorPatterns.length > 0 ? errorPatterns.map((pattern, i) => ` #### Pattern ${i + 1}: ${pattern.type} - **Occurrences**: ${pattern.count} - **Example Message**: ${pattern.example} - **Likely Cause**: ${pattern.cause} - **Suggested Fix**: ${pattern.fix} `).join('') : 'No error patterns detected during analysis window'} ### Memory Analysis ${trackMemoryLeaks ? ` **Memory Pattern**: ${memoryPattern.trend} - **Starting Memory**: ${memoryPattern.start}MB - **Peak Memory**: ${memoryPattern.peak}MB - **End Memory**: ${memoryPattern.end}MB - **Growth Rate**: ${memoryPattern.growthRate}MB/min - **Leak Detected**: ${memoryPattern.possibleLeak ? '⚠️ Yes' : '✅ No'} ` : 'Memory leak tracking disabled'} ### Recent Errors ${analysisData.errors.slice(-5).map((error, i) => ` #### Error ${i + 1} - **Time**: ${new Date(error.timestamp).toISOString()} - **Message**: ${error.message} ${includeStackTraces ? `- **Stack**: ${error.stack?.split('\\n').slice(0, 3).join('\\n')}...` : ''} `).join('')} ### GraphQL Correlation ${correlateWithGraphQL ? ` To correlate process deaths with GraphQL failures, run: \`analyze_failed_graphql_queries\` with the same sessionId Common correlation patterns: - Process dies immediately after GraphQL 500 error - WebSocket disconnection following GraphQL timeout - Memory spike during large GraphQL response processing ` : 'GraphQL correlation disabled'} ### Common Death Scenarios #### 1. Mock Access Failures **Symptoms**: Process dies when accessing missing mocks **Fix**: Ensure mocks are accessible across all processes **Tool**: Use \`inspect_ets_operations\` to verify mock state #### 2. Message Queue Overflow **Symptoms**: Process becomes unresponsive then dies **Fix**: Implement proper message queue management **Tool**: Use \`monitor_message_passing\` to track queue sizes #### 3. Memory Leaks **Symptoms**: Gradual memory increase then sudden death **Fix**: Review assigns and temporary state management **Tool**: Monitor memory patterns in production #### 4. WebSocket Disconnections **Symptoms**: Clean disconnection but unexpected timing **Fix**: Review connection stability and heartbeat settings **Tool**: Use \`track_liveview_lifecycle\` with longer duration ### Recommendations ${identifyCommonPatterns ? this.generateDeathRecommendations(errorPatterns, memoryPattern) : 'Pattern analysis disabled'} ### Next Steps 1. **Implement Error Boundaries**: Add LiveView error handling 2. **Review Mock Accessibility**: Ensure all processes can access required mocks 3. **Monitor Message Queues**: Track queue sizes and processing times 4. **Add Telemetry**: Implement Phoenix telemetry for server-side monitoring 5. **Memory Profiling**: Use BEAM memory analysis tools in development ### Backend Integration For complete process death analysis, integrate with: - Phoenix telemetry events for process monitoring - BEAM VM observer for memory and process inspection - ETS table monitoring for state access patterns - GenServer supervisor tree analysis` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ Process Death Analysis Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } async monitorMessagePassing(args, session) { const { monitorDuration = 20000, trackMessageQueue = true, identifyStuckMessages = true, captureMessageContent = true, trackProcessCommunication = true, alertOnQueueBacklog = true, queueBacklogThreshold = 50 } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } // Inject message monitoring await page.addInitScript((config) => { window.__messageMonitor = { messages: [], queues: new Map(), communications: [], config: config }; // Monitor WebSocket messages (LiveView communication) const originalWebSocket = window.WebSocket; window.WebSocket = class extends originalWebSocket { constructor(url, protocols) { super(url, protocols); const socketId = Math.random().toString(36).substr(2, 9); window.__messageMonitor.queues.set(socketId, { pending: [], processed: [], size: 0 }); this.addEventListener('message', (event) => { const messageId = Math.random().toString(36).substr(2, 9); const timestamp = Date.now(); try { const data = JSON.parse(event.data); const message = { id: messageId, socketId, timestamp, type: 'received', topic: data[1] || 'unknown', event: data[0] || 'unknown', content: config.captureMessageContent ? data : '[content hidden]', processed: false }; window.__messageMonitor.messages.push(message); // Add to queue tracking const queue = window.__messageMonitor.queues.get(socketId); if (queue) { queue.pending.push(messageId); queue.size = queue.pending.length; window.__messageMonitor.queues.set(socketId, queue); } // Simulate message processing (in real app, this would be actual processing) setTimeout(() => { const queue = window.__messageMonitor.queues.get(socketId); if (queue) { const index = queue.pending.indexOf(messageId); if (index > -1) { queue.pending.splice(index, 1); queue.processed.push(messageId); queue.size = queue.pending.length; window.__messageMonitor.queues.set(socketId, queue); } } message.processed = true; }, Math.random() * 100 + 10); // Simulate processing time } catch (e) { // Handle non-JSON messages const message = { id: messageId, socketId, timestamp, type: 'received', content: config.captureMessageContent ? event.data : '[content hidden]', processed: true // Non-JSON messages are "processed" immediately }; window.__messageMonitor.messages.push(message); } }); // Monitor outgoing messages const originalSend = this.send; this.send = function (data) { const messageId = Math.random().toString(36).substr(2, 9); try { const parsedData = JSON.parse(data); const message = { id: messageId, socketId, timestamp: Date.now(), type: 'sent', topic: parsedData[1] || 'unknown', event: parsedData[0] || 'unknown', content: config.captureMessageContent ? parsedData : '[content hidden]', processed: true }; window.__messageMonitor.messages.push(message); // Track communication patterns window.__messageMonitor.communications.push({ from: socketId, to: 'server', messageId, timestamp: Date.now() }); } catch (e) { // Handle non-JSON data } return originalSend.call(this, data); }; } }; // Alert on queue backlog if (config.alertOnQueueBacklog) { setInterval(() => { window.__messageMonitor.queues.forEach((queue, socketId) => { if (queue.size > config.queueBacklogThreshold) { console.warn(`Queue backlog alert: Socket ${socketId} has ${queue.size} pending messages`); } }); }, 1000); } }, { trackMessageQueue, identifyStuckMessages, captureMessageContent, trackProcessCommunication, alertOnQueueBacklog, queueBacklogThreshold }); // Monitor for the specified duration await new Promise(resolve => setTimeout(resolve, monitorDuration)); // Get monitoring data const monitorData = await page.evaluate(() => { return { messages: window.__messageMonitor?.messages || [], queues: Array.from(window.__messageMonitor?.queues?.entries() || []), communications: window.__messageMonitor?.communications || [] }; }); // Analyze message patterns const stuckMessages = identifyStuckMessages ? monitorData.messages.filter((msg) => !msg.processed && Date.now() - msg.timestamp > 5000) : []; const queueStats = this.calculateQueueStats(monitorData.queues); const communicationPatterns = this.analyzeCommunicationPatterns(monitorData.communications); return { content: [{ type: 'text', text: `## 📨 Message Passing Monitor Report ### Monitoring Summary - **Monitor Duration**: ${monitorDuration}ms - **Total Messages**: ${monitorData.messages.length} - **Active Queues**: ${monitorData.queues.length} - **Communication Events**: ${monitorData.communications.length} - **Stuck Messages**: ${stuckMessages.length} ### Queue Statistics ${trackMessageQueue ? ` ${monitorData.queues.map(([socketId, queue]) => ` #### Queue ${socketId} - **Current Size**: ${queue.size} - **Total Processed**: ${queue.processed.length} - **Pending**: ${queue.pending.length} - **Status**: ${queue.size > queueBacklogThreshold ? '⚠️ Backlogged' : '✅ Normal'} `).join('') || 'No message queues tracked'} ` : 'Message queue tracking disabled'} ### Stuck Messages Analysis ${identifyStuckMessages ? ` ${stuckMessages.length > 0 ? stuckMessages.map((msg, i) => ` #### Stuck Message ${i + 1} - **ID**: ${msg.id} - **Socket**: ${msg.socketId} - **Age**: ${Date.now() - msg.timestamp}ms - **Topic**: ${msg.topic} - **Event**: ${msg.event} - **Type**: ${msg.type} `).join('') : '✅ No stuck messages detected'} ` : 'Stuck message identification disabled'} ### Communication Patterns ${trackProcessCommunication ? ` **Pattern Analysis**: ${communicationPatterns.summary} - **Most Active Socket**: ${communicationPatterns.mostActive} - **Communication Frequency**: ${communicationPatterns.frequency} messages/second - **Bidirectional**: ${communicationPatterns.bidirectional ? '✅' : '❌'} ` : 'Communication pattern tracking disabled'} ### Recent Messages ${monitorData.messages.slice(-10).map((msg, i) => ` ${i + 1}. **${msg.type}** - ${msg.event} (${msg.topic}) - Time: ${new Date(msg.timestamp).toISOString()} - Socket: ${msg.socketId} - Processed: ${msg.processed ? '✅' : '⏳'} ${captureMessageContent ? `- Content: ${JSON.stringify(msg.content, null, 2).slice(0, 100)}...` : ''} `).join('')} ### Queue Performance Metrics - **Average Queue Size**: ${queueStats.averageSize} - **Peak Queue Size**: ${queueStats.peakSize} - **Total Messages Processed**: ${queueStats.totalProcessed} - **Processing Rate**: ${queueStats.processingRate} messages/second ### Alerts and Issues ${alertOnQueueBacklog ? ` **Queue Backlog Alerts**: ${queueStats.backlogAlerts} alerts triggered **Threshold**: ${queueBacklogThreshold} messages ` : 'Queue backlog alerts disabled'} ### Message Flow Analysis 1. **Inbound Messages**: ${monitorData.messages.filter((m) => m.type === 'received').length} 2. **Outbound Messages**: ${monitorData.messages.filter((m) => m.type === 'sent').length} 3. **Processing Delays**: ${monitorData.messages.filter((m) => !m.processed).length} unprocessed 4. **Communication Health**: ${stuckMessages.length === 0 ? '✅ Healthy' : '⚠️ Issues Detected'} ### Recommendations ${this.generateMessagePassingRecommendations(stuckMessages, queueStats, communicationPatterns)} ### Configuration - **Track Message Queue**: ${trackMessageQueue ? '✅' : '❌'} - **Identify Stuck Messages**: ${identifyStuckMessages ? '✅' : '❌'} - **Capture Message Content**: ${captureMessageContent ? '✅' : '❌'} - **Track Process Communication**: ${trackProcessCommunication ? '✅' : '❌'} - **Queue Backlog Alerts**: ${alertOnQueueBacklog ? '✅' : '❌'} ### Next Steps - Use \`inspect_ets_operations\` if message processing involves ETS tables - Use \`track_liveview_lifecycle\` to correlate with process lifecycle events - Consider implementing message prioritization for high-priority operations - Review LiveView event handling for potential bottlenecks` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ Message Passing Monitor Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } async inspectETSOperations(args, session) { const { inspectionDuration = 15000, focusedTables = [], trackTableAccess = true, captureStateChanges = true, identifyAccessPatterns = true, trackMockState = true, alertOnAccessFailures = true } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } // Note: This is a frontend simulation of ETS operations // In a real implementation, this would integrate with the Elixir backend await page.addInitScript((config) => { window.__etsInspector = { operations: [], tables: new Map(), accessPatterns: [], mockOperations: [], config: config }; // Simulate ETS-like operations using localStorage and sessionStorage const originalSetItem = localStorage.setItem; const originalGetItem = localStorage.getItem; const originalRemoveItem = localStorage.removeItem; localStorage.setItem = function (key, value) { const operation = { id: Math.random().toString(36).substr(2, 9), type: 'insert', table: 'localStorage', key, value: config.captureStateChanges ? value : '[value hidden]', timestamp: Date.now(), success: true }; window.__etsInspector.operations.push(operation); // Track mock-related operations if (config.trackMockState && (key.includes('mock') || key.includes('test'))) { window.__etsInspector.mockOperations.push(operation); } return originalSetItem.call(this, key, value); }; localStorage.getItem = function (key) { const result = originalGetItem.call(this, key); const operation = { id: Math.random().toString(36).substr(2, 9), type: 'lookup', table: 'localStorage', key, value: config.captureStateChanges ? result : '[value hidden]', timestamp: Date.now(), success: result !== null }; window.__etsInspector.operations.push(operation); // Alert on access failures if (config.alertOnAccessFailures && result === null) { console.warn(`ETS access failure: Key '${key}' not found in localStorage`); } // Track access patterns if (config.identifyAccessPatterns) { window.__etsInspector.accessPatterns.push({ table: 'localStorage', key, timestamp: Date.now(), success: result !== null }); } return result; }; localStorage.removeItem = function (key) { const operation = { id: Math.random().toString(36).substr(2, 9), type: 'delete', table: 'localStorage', key, timestamp: Date.now(), success: true }; window.__etsInspector.operations.push(operation); return originalRemoveItem.call(this, key); }; // Also monitor sessionStorage const originalSessionSetItem = sessionStorage.setItem; const originalSessionGetItem = sessionStorage.getItem; sessionStorage.setItem = function (key, value) { const operation = { id: Math.random().toString(36).substr(2, 9), type: 'insert', table: 'sessionStorage', key, value: config.captureStateChanges ? value : '[value hidden]', timestamp: Date.now(), success: true }; window.__etsInspector.operations.push(operation); return originalSessionSetItem.call(this, key, value); }; sessionStorage.getItem = function (key) { const result = originalSessionGetItem.call(this, key); const operation = { id: Math.random().toString(36).substr(2, 9), type: 'lookup', table: 'sessionStorage', key, value: config.captureStateChanges ? result : '[value hidden]', timestamp: Date.now(), success: result !== null }; window.__etsInspector.operations.push(operation); return result; }; }, { focusedTables, trackTableAccess, captureStateChanges, identifyAccessPatterns, trackMockState, alertOnAccessFailures }); // Monitor for the specified duration await new Promise(resolve => setTimeout(resolve, inspectionDuration)); // Get ETS inspection data const etsData = await page.evaluate(() => { return { operations: window.__etsInspector?.operations || [], accessPatterns: window.__etsInspector?.accessPatterns || [], mockOperations: window.__etsInspector?.mockOperations || [] }; }); // Analyze ETS patterns const tableStats = this.calculateTableStats(etsData.operations); const accessFailures = etsData.operations.filter((op) => !op.success); const mockAccessIssues = this.analyzeMockAccess(etsData.mockOperations); return { content: [{ type: 'text', text: `## 🗄️ ETS Operations Inspector Report ### Inspection Summary - **Inspection Duration**: ${inspectionDuration}ms - **Total Operations**: ${etsData.operations.length} - **Access Patterns**: ${etsData.accessPatterns.length} - **Mock Operations**: ${etsData.mockOperations.length} - **Access Failures**: ${accessFailures.length} ### Table Access Statistics ${trackTableAccess ? ` ${Object.entries(tableStats).map(([table, stats]) => ` #### Table: ${table} - **Total Operations**: ${stats.total} - **Inserts**: ${stats.inserts} - **Lookups**: ${stats.lookups} - **Deletes**: ${stats.deletes} - **Success Rate**: ${stats.successRate}% - **Most Active Key**: ${stats.mostActiveKey} `).join('') || 'No table statistics available'} ` : 'Table access tracking disabled'} ### Access Failures Analysis ${accessFailures.length > 0 ? ` **Failed Operations**: ${accessFailures.length} ${accessFailures.slice(0, 5).map((failure, i) => ` #### Failure ${i + 1} - **Table**: ${failure.table} - **Operation**: ${failure.type} - **Key**: ${failure.key} - **Time**: ${new Date(failure.timestamp).toISOString()} - **Likely Cause**: ${this.determinateFailureCause(failure)} `).join('')} ` : '✅ No access failures detected'} ### Mock State Analysis ${trackMockState ? ` **Mock Operations Detected**: ${etsData.mockOperations.length} ${etsData.mockOperations.length > 0 ? ` **Mock Access Patterns**: ${mockAccess