# 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)
```