# Workflow 03: Debug Performance Issues **Purpose**: Diagnose and resolve performance issues in DSS dashboard and API **When to Use**: - Dashboard loads slowly - API requests taking too long - Browser becomes unresponsive - High memory usage warnings - Long task warnings in logs **Estimated Time**: 15-45 minutes --- ## Prerequisites - Browser logger active (window.__DSS_BROWSER_LOGS available) - Access to server logs and metrics - Basic understanding of performance metrics - DevTools Performance panel knowledge --- ## Step-by-Step Procedure ### Step 1: Gather Performance Baseline **Browser Performance Metrics**: ```javascript // Get diagnostic with performance data const diag = window.__DSS_BROWSER_LOGS.diagnostic(); console.table({ 'Uptime (ms)': diag.uptime, 'Total Logs': diag.totalLogs, 'Network Requests': diag.networkRequests, 'Memory Used (MB)': (diag.memory.usedJSHeapSize / 1024 / 1024).toFixed(2), 'Memory Limit (MB)': (diag.memory.jsHeapSizeLimit / 1024 / 1024).toFixed(2), 'Memory Usage %': diag.memory.usagePercent }); // Get performance entries const perfMetrics = window.__DSS_BROWSER_LOGS.getLogs({ category: 'performance' }); console.table(perfMetrics); ``` **Expected Baseline**: - Page load: <2000ms - DOM content loaded: <500ms - API requests: <200ms each - Memory usage: <50% - No long tasks >100ms **Performance Issues Indicators**: - Page load >5000ms → Slow initial load (Step 2) - API requests >1000ms → Slow API (Step 3) - Memory usage >80% → Memory leak (Step 4) - Multiple long tasks >100ms → CPU bottleneck (Step 5) --- ### Step 2: Diagnose Slow Page Load **Get Page Load Metrics**: ```javascript const perfData = performance.getEntriesByType('navigation')[0]; console.table({ 'DNS Lookup (ms)': perfData.domainLookupEnd - perfData.domainLookupStart, 'TCP Connection (ms)': perfData.connectEnd - perfData.connectStart, 'Request (ms)': perfData.responseStart - perfData.requestStart, 'Response (ms)': perfData.responseEnd - perfData.responseStart, 'DOM Processing (ms)': perfData.domInteractive - perfData.domLoading, 'DOM Content Loaded (ms)': perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart, 'Total Load (ms)': perfData.loadEventEnd - perfData.fetchStart }); ``` **Diagnosis Matrix**: | Slow Phase | Cause | Solution | |------------|-------|----------| | DNS Lookup >100ms | DNS issues | Check DNS settings, use different DNS | | TCP Connection >200ms | Network latency | Check connection, use CDN | | Response >1000ms | Large HTML file | Minify HTML, lazy load components | | DOM Processing >2000ms | Heavy JavaScript | Code splitting, lazy imports | | DOM Content Loaded >500ms | Blocking scripts | Async/defer scripts, move to bottom | **Common Fixes**: #### Issue 1: Large Initial Bundle ```javascript // Check resource sizes performance.getEntriesByType('resource').forEach(r => { if (r.transferSize > 100000) { // >100KB console.log(`Large file: ${r.name} (${(r.transferSize / 1024).toFixed(2)} KB)`); } }); ``` **Solution**: - Split large JavaScript files - Use code splitting with dynamic imports - Compress assets (gzip/brotli) --- #### Issue 2: Blocking Scripts ```html ``` --- ### Step 3: Diagnose Slow API Requests **Get Network Performance**: ```javascript const network = window.__DSS_BROWSER_LOGS.network(); const slowRequests = network.filter(r => r.data.duration > 500); console.group('Slow Requests (>500ms)'); console.table(slowRequests.map(r => ({ URL: r.data.url, Method: r.data.method, Status: r.data.status, Duration: r.data.duration + 'ms' }))); console.groupEnd(); ``` **Server-Side Check**: ```bash # Check API response times in server logs journalctl -u dss-api -n 200 | grep "INFO.*GET\|POST" # Check database query times (if logged) journalctl -u dss-api -n 200 | grep "query took" ``` **Common Slow API Issues**: #### Issue 1: Database Query Slow (N+1 Problem) ```python # Bad: N+1 queries for project in projects: components = get_components(project.id) # Separate query each time # Good: Single query with JOIN components = get_all_components_with_projects() ``` **Diagnosis**: ```bash # Enable SQLite query logging sqlite3 .dss/dss.db .log stdout .timer on SELECT * FROM Projects; ``` **Solution**: - Use JOINs instead of multiple queries - Add indexes on frequently queried columns - Cache repeated queries --- #### Issue 2: Large Response Payload ```javascript // Check response sizes network.forEach(r => { if (r.data.headers && r.data.headers['content-length']) { const sizeKB = parseInt(r.data.headers['content-length']) / 1024; if (sizeKB > 100) { console.log(`Large response: ${r.data.url} (${sizeKB.toFixed(2)} KB)`); } } }); ``` **Solution**: - Implement pagination (limit results to 50-100 items) - Use field selection (only return needed fields) - Compress responses (gzip) - Add API caching --- #### Issue 3: Synchronous Processing ```python # Bad: Synchronous heavy processing def get_analysis(): data = fetch_all_data() analysis = process_data(data) # Blocking, takes 5 seconds return analysis # Good: Async or background job async def get_analysis(): data = await fetch_all_data() # Trigger background job, return immediately job_id = queue_analysis(data) return {"status": "processing", "job_id": job_id} ``` --- ### Step 4: Diagnose Memory Leaks **Check Memory Usage**: ```javascript // Get current memory const mem = performance.memory; console.table({ 'Used (MB)': (mem.usedJSHeapSize / 1024 / 1024).toFixed(2), 'Total (MB)': (mem.totalJSHeapSize / 1024 / 1024).toFixed(2), 'Limit (MB)': (mem.jsHeapSizeLimit / 1024 / 1024).toFixed(2), 'Usage %': ((mem.usedJSHeapSize / mem.jsHeapSizeLimit) * 100).toFixed(2) }); // Monitor over time let memorySnapshots = []; setInterval(() => { const m = performance.memory; memorySnapshots.push({ time: Date.now(), used: m.usedJSHeapSize }); if (memorySnapshots.length > 20) memorySnapshots.shift(); // Check if memory is growing const first = memorySnapshots[0].used; const last = memorySnapshots[memorySnapshots.length - 1].used; const growth = ((last - first) / first * 100).toFixed(2); console.log(`Memory growth over ${memorySnapshots.length} checks: ${growth}%`); }, 5000); ``` **Memory Leak Indicators**: - Memory usage steadily increasing (>10% per minute) - Memory warnings in browser logs - Browser becoming slow/unresponsive over time **Common Memory Leak Causes**: #### Cause 1: Event Listeners Not Removed ```javascript // Bad: Creates new listener on each render, never removes function render() { window.addEventListener('resize', handleResize); } // Good: Remove old listener let resizeHandler = null; function render() { if (resizeHandler) { window.removeEventListener('resize', resizeHandler); } resizeHandler = handleResize; window.addEventListener('resize', resizeHandler); } ``` --- #### Cause 2: Detached DOM Nodes ```javascript // Bad: References keep DOM nodes in memory let cachedNodes = []; function cacheNode(node) { cachedNodes.push(node); // Node stays in memory even if removed from DOM } // Good: Use WeakMap for node cache let cachedNodes = new WeakMap(); function cacheNode(node, data) { cachedNodes.set(node, data); // Auto-removed when node is GC'd } ``` --- #### Cause 3: Timers Not Cleared ```javascript // Bad: Timer keeps running even after component unmounted setInterval(() => { updateData(); }, 1000); // Good: Clear timer on unmount let timerId = null; function startTimer() { timerId = setInterval(updateData, 1000); } function stopTimer() { if (timerId) clearInterval(timerId); } ``` **Diagnosis Tools**: 1. Chrome DevTools → Memory → Take heap snapshot 2. Compare snapshots over time 3. Look for "Detached DOM tree" entries 4. Find objects growing in number --- ### Step 5: Diagnose CPU Bottlenecks **Get Long Tasks**: ```javascript const longTasks = window.__DSS_BROWSER_LOGS.getLogs({ category: 'longTask', limit: 50 }); console.group('Long Tasks (>50ms)'); console.table(longTasks.map(t => ({ Name: t.data.name, Duration: t.data.duration.toFixed(2) + 'ms', Time: new Date(t.timestamp).toLocaleTimeString() }))); console.groupEnd(); ``` **Performance Profiling**: 1. Open DevTools → Performance 2. Click Record 3. Perform slow action 4. Stop recording 5. Analyze flame graph for long tasks **Common CPU Bottlenecks**: #### Issue 1: Synchronous Loop Over Large Array ```javascript // Bad: Blocks UI for large arrays function processItems(items) { items.forEach(item => { expensiveOperation(item); // If items.length = 10000, UI freezes }); } // Good: Batch processing with breaks async function processItems(items) { const batchSize = 100; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); batch.forEach(item => expensiveOperation(item)); await new Promise(resolve => setTimeout(resolve, 0)); // Give UI a break } } ``` --- #### Issue 2: Frequent DOM Manipulation ```javascript // Bad: Multiple reflows for (let i = 0; i < 1000; i++) { const div = document.createElement('div'); div.textContent = i; container.appendChild(div); // Reflow on each append } // Good: Single reflow with fragment const fragment = document.createDocumentFragment(); for (let i = 0; i < 1000; i++) { const div = document.createElement('div'); div.textContent = i; fragment.appendChild(div); } container.appendChild(fragment); // Single reflow ``` --- #### Issue 3: Inefficient Rendering ```javascript // Bad: Re-render entire list on every change function renderList(items) { container.innerHTML = ''; // Destroy all items.forEach(item => { container.appendChild(createItem(item)); // Recreate all }); } // Good: Update only changed items (use virtual DOM or diff) function renderList(items, previousItems) { const changes = diff(items, previousItems); changes.forEach(change => { if (change.type === 'add') { container.appendChild(createItem(change.item)); } else if (change.type === 'remove') { change.element.remove(); } else if (change.type === 'update') { updateItem(change.element, change.item); } }); } ``` --- ### Step 6: Server-Side Performance Check **Check Server Resource Usage**: ```bash # CPU usage top -b -n 1 | grep "uvicorn\|python" # Memory usage ps aux --sort=-%mem | grep "uvicorn\|python" | head -5 # Disk I/O iostat -x 1 5 # Network iftop -t -s 10 ``` **Check Database Performance**: ```bash # Database size ls -lh .dss/dss.db # Table sizes sqlite3 .dss/dss.db << EOF SELECT name, COUNT(*) as row_count FROM sqlite_master sm LEFT JOIN pragma_table_info(sm.name) ON 1=1 WHERE sm.type='table' GROUP BY name; EOF # Check for missing indexes sqlite3 .dss/dss.db << EOF SELECT name, sql FROM sqlite_master WHERE type='index' AND sql IS NOT NULL; EOF ``` **Database Optimization**: ```bash # Vacuum to reclaim space and reorganize sqlite3 .dss/dss.db "VACUUM;" # Analyze to update statistics sqlite3 .dss/dss.db "ANALYZE;" # Check index usage (run slow query with EXPLAIN QUERY PLAN) sqlite3 .dss/dss.db << EOF EXPLAIN QUERY PLAN SELECT * FROM Projects WHERE name LIKE '%test%'; EOF ``` --- ## Performance Optimization Checklist ### Browser Optimizations - [ ] Code splitting implemented - [ ] Lazy loading for routes/components - [ ] Images optimized and lazy-loaded - [ ] Scripts deferred or async - [ ] CSS minified and critical CSS inlined - [ ] Service worker for caching - [ ] Event listeners properly cleaned up - [ ] No memory leaks detected ### API Optimizations - [ ] Database queries optimized (indexes, JOINs) - [ ] Response pagination implemented - [ ] API caching enabled - [ ] Compression enabled (gzip/brotli) - [ ] Connection pooling configured - [ ] Async processing for heavy tasks - [ ] Rate limiting to prevent abuse ### System Optimizations - [ ] Database vacuumed and analyzed - [ ] Log rotation configured - [ ] Disk space sufficient (>20% free) - [ ] Memory sufficient (>30% free) - [ ] Supervisord restart policies configured --- ## Success Criteria - ✅ Page load <2000ms - ✅ API requests <200ms - ✅ Memory usage <50% - ✅ No long tasks >100ms - ✅ No memory growth over time - ✅ Smooth scrolling and interactions --- ## Performance Metrics to Track **Browser**: - First Contentful Paint (FCP): <1000ms - Largest Contentful Paint (LCP): <2500ms - Time to Interactive (TTI): <3000ms - Total Blocking Time (TBT): <200ms - Cumulative Layout Shift (CLS): <0.1 **API**: - Response time p50: <100ms - Response time p95: <500ms - Response time p99: <1000ms - Throughput: >100 req/sec - Error rate: <1% **Database**: - Query time p50: <10ms - Query time p95: <50ms - Query time p99: <100ms - Connection pool usage: <80% --- ## Next Steps - If performance acceptable: Document baseline for monitoring - If still slow: Use Chrome Performance Profiler for deeper analysis - If database slow: Consider adding indexes or caching layer - If memory leaks: Use Chrome Memory Profiler to find retaining paths - Schedule regular performance audits (monthly) --- ## Related Documentation - `.dss/MCP_DEBUG_TOOLS_ARCHITECTURE.md` - Performance monitoring in MCP - `admin-ui/js/core/browser-logger.js` - Performance capture implementation - Web Vitals: https://web.dev/vitals/ --- ## MCP Tool Access **From Claude Code**: ``` Use tool: dss_get_browser_diagnostic (includes memory metrics) Use tool: dss_get_server_diagnostic (includes performance metrics) ```