Files
gh-rubencodeforges-codeforg…/skills/runtime-debugging.md
2025-11-30 08:53:15 +08:00

13 KiB

Runtime Debugging Patterns

Advanced techniques for debugging web application freezes, hangs, and performance issues.

Common Runtime Issues

1. Memory Leaks

Symptoms:

  • Gradually increasing memory usage
  • Browser tab becomes unresponsive over time
  • Page crashes with "Aw, Snap!" error

Common Causes:

// Detached DOM nodes
let elements = [];
document.addEventListener('click', () => {
  const div = document.createElement('div');
  document.body.appendChild(div);
  elements.push(div); // Keeping reference after removal
  document.body.removeChild(div); // DOM node removed but still in memory
});

// Event listener accumulation
class Component {
  constructor() {
    // Missing cleanup in componentWillUnmount/ngOnDestroy
    window.addEventListener('resize', this.handleResize);
  }
}

// Closure traps
function createLeak() {
  const largeData = new Array(1000000).fill('data');
  return function() {
    // largeData is retained even if not used
    console.log('callback');
  };
}

Detection:

  • Use Chrome DevTools Memory Profiler
  • Take heap snapshots and compare
  • Look for objects with increasing retain counts

2. Infinite Loops

JavaScript Infinite Loops:

// Synchronous infinite loop - blocks immediately
while (condition) {
  // condition never becomes false
}

// Recursive setTimeout - gradual degradation
function loop() {
  // Do something
  setTimeout(loop, 0); // Keeps scheduling
}

Angular Change Detection Loops:

// Component causing infinite change detection
@Component({
  template: '{{ getComputedValue() }}'
})
class BadComponent {
  getComputedValue() {
    // Returns different value each time
    return Math.random(); // Triggers new change detection
  }
}

// Zone.js microtask explosion
someObservable.subscribe(value => {
  this.property = value;
  // Triggers change detection
  this.anotherObservable.next(this.property);
  // Which triggers another change...
});

React Re-render Loops:

// Missing dependencies
function Component() {
  const [state, setState] = useState(0);

  useEffect(() => {
    setState(state + 1); // Missing dependency array
  }); // Runs on every render

  return <div>{state}</div>;
}

// Object/Array recreation
function Parent() {
  // New object every render
  const config = { key: 'value' };

  return <Child config={config} />;
  // Child re-renders every time
}

3. Main Thread Blocking

Long-Running JavaScript:

// Heavy computation on main thread
function processLargeDataset(data) {
  for (let i = 0; i < 1000000; i++) {
    // Complex calculations
    data[i] = expensiveOperation(data[i]);
  }
}

// Solution: Use Web Workers
const worker = new Worker('processor.js');
worker.postMessage({ cmd: 'process', data: largeData });
worker.onmessage = (e) => {
  // Handle processed data
};

// Or chunk the work
async function processInChunks(data) {
  const chunkSize = 100;
  for (let i = 0; i < data.length; i += chunkSize) {
    const chunk = data.slice(i, i + chunkSize);
    processChunk(chunk);
    // Yield to browser
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

4. Network Request Issues

Request Queue Blocking:

// Browser limits concurrent requests (6 per domain)
for (let i = 0; i < 100; i++) {
  fetch(`/api/item/${i}`); // Only 6 run in parallel
}

// Solution: Batch requests
const ids = Array.from({ length: 100 }, (_, i) => i);
fetch('/api/items/batch', {
  method: 'POST',
  body: JSON.stringify({ ids })
});

CORS Preflight Accumulation:

// Each cross-origin request triggers OPTIONS
for (let endpoint of endpoints) {
  fetch(endpoint, {
    headers: {
      'Custom-Header': 'value' // Triggers preflight
    }
  });
}

Framework-Specific Patterns

Angular Debugging

Zone.js Task Tracking:

// Monitor Zone.js tasks
Zone.current.fork({
  name: 'debugZone',
  onScheduleTask: (delegate, current, target, task) => {
    console.log('Task scheduled:', task.type, task.source);
    return delegate.scheduleTask(target, task);
  },
  onInvokeTask: (delegate, current, target, task, applyThis, applyArgs) => {
    console.log('Task invoked:', task.type);
    return delegate.invokeTask(target, task, applyThis, applyArgs);
  }
}).run(() => {
  // Your Angular app code
});

Change Detection Profiling:

// Enable Angular DevTools Profiler
import { enableDebugTools } from '@angular/platform-browser';
import { ApplicationRef } from '@angular/core';

platformBrowserDynamic().bootstrapModule(AppModule)
  .then(module => {
    const appRef = module.injector.get(ApplicationRef);
    const componentRef = appRef.components[0];
    enableDebugTools(componentRef);
    // Now use: ng.profiler.timeChangeDetection()
  });

React Debugging

React Profiler API:

import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}

<Profiler id="App" onRender={onRenderCallback}>
  <App />
</Profiler>

Why Did You Render:

// Setup why-did-you-render
import React from 'react';
if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  });
}

// Track specific component
Component.whyDidYouRender = true;

Vue Debugging

Vue Performance Tracking:

// Vue 2
Vue.config.performance = true;

// Vue 3
const app = createApp(App);
app.config.performance = true;

// Now use Performance DevTools to see component timings

Browser APIs for Debugging

Performance Observer API

// Monitor long tasks
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 50) {
      console.warn('Long task detected:', {
        duration: entry.duration,
        startTime: entry.startTime,
        name: entry.name
      });

      // Send to analytics
      analytics.track('long_task', {
        duration: entry.duration,
        url: window.location.href
      });
    }
  }
});

observer.observe({
  entryTypes: ['longtask', 'measure', 'navigation']
});

Memory API

// Monitor memory usage (Chrome only)
if (performance.memory) {
  setInterval(() => {
    const memInfo = performance.memory;
    const usedMB = Math.round(memInfo.usedJSHeapSize / 1048576);
    const totalMB = Math.round(memInfo.totalJSHeapSize / 1048576);
    const limitMB = Math.round(memInfo.jsHeapSizeLimit / 1048576);

    console.log(`Memory: ${usedMB}/${totalMB}MB (limit: ${limitMB}MB)`);

    // Alert if using >90% of limit
    if (memInfo.usedJSHeapSize > memInfo.jsHeapSizeLimit * 0.9) {
      console.error('Memory usage critical!');
    }
  }, 5000);
}

Request Idle Callback

// Defer non-critical work
const tasksQueue = [];

function scheduleTasks(tasks) {
  tasksQueue.push(...tasks);
  processTaskQueue();
}

function processTaskQueue(deadline) {
  while (deadline.timeRemaining() > 0 && tasksQueue.length > 0) {
    const task = tasksQueue.shift();
    task();
  }

  if (tasksQueue.length > 0) {
    requestIdleCallback(processTaskQueue);
  }
}

requestIdleCallback(processTaskQueue);

Headless Browser Debugging

Puppeteer Performance Monitoring

const puppeteer = require('puppeteer');

async function analyzePerformance(url) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // Enable CDP domains
  const client = await page.target().createCDPSession();
  await client.send('Performance.enable');
  await client.send('Network.enable');

  // Collect metrics
  const metrics = [];

  // Monitor metrics over time
  const interval = setInterval(async () => {
    const { metrics: perfMetrics } = await client.send('Performance.getMetrics');
    const timestamp = Date.now();

    metrics.push({
      timestamp,
      heap: perfMetrics.find(m => m.name === 'JSHeapUsedSize').value,
      documents: perfMetrics.find(m => m.name === 'Documents').value,
      frames: perfMetrics.find(m => m.name === 'Frames').value,
      nodes: perfMetrics.find(m => m.name === 'Nodes').value,
      layoutCount: perfMetrics.find(m => m.name === 'LayoutCount').value,
      jsEventListeners: perfMetrics.find(m => m.name === 'JSEventListeners').value
    });
  }, 1000);

  // Navigate and wait
  await page.goto(url, { waitUntil: 'networkidle0' });
  await page.waitForTimeout(10000);

  clearInterval(interval);

  // Analyze trends
  const analysis = analyzeMetricTrends(metrics);

  await browser.close();
  return analysis;
}

function analyzeMetricTrends(metrics) {
  const heapGrowth = metrics[metrics.length - 1].heap - metrics[0].heap;
  const avgHeapGrowthPerSec = heapGrowth / metrics.length;

  return {
    memoryLeak: avgHeapGrowthPerSec > 100000, // >100KB/s growth
    domLeak: metrics[metrics.length - 1].nodes > 1500,
    listenerLeak: metrics[metrics.length - 1].jsEventListeners > 500,
    metrics: metrics
  };
}

Playwright Network Monitoring

const { chromium } = require('playwright');

async function monitorNetworkPerformance(url) {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  const slowRequests = [];
  const failedRequests = [];

  // Monitor all requests
  page.on('request', request => {
    request._startTime = Date.now();
  });

  page.on('response', response => {
    const request = response.request();
    const duration = Date.now() - request._startTime;

    if (duration > 3000) {
      slowRequests.push({
        url: request.url(),
        method: request.method(),
        duration: duration,
        status: response.status()
      });
    }
  });

  page.on('requestfailed', request => {
    failedRequests.push({
      url: request.url(),
      method: request.method(),
      failure: request.failure()
    });
  });

  await page.goto(url);
  await page.waitForTimeout(10000);

  await browser.close();

  return { slowRequests, failedRequests };
}

Production Monitoring

Client-Side Monitoring Script

// Add to your production app
(function() {
  const perfData = {
    longTasks: [],
    errors: [],
    metrics: []
  };

  // Long task monitoring
  if (window.PerformanceObserver) {
    new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        perfData.longTasks.push({
          duration: entry.duration,
          timestamp: Date.now()
        });

        // Send to backend if critical
        if (entry.duration > 1000) {
          sendToAnalytics('critical_long_task', {
            duration: entry.duration,
            url: window.location.href
          });
        }
      }
    }).observe({ entryTypes: ['longtask'] });
  }

  // Error monitoring
  window.addEventListener('error', (e) => {
    perfData.errors.push({
      message: e.message,
      stack: e.error?.stack,
      timestamp: Date.now()
    });

    sendToAnalytics('js_error', {
      message: e.message,
      url: window.location.href
    });
  });

  // Periodic metrics collection
  setInterval(() => {
    if (performance.memory) {
      perfData.metrics.push({
        heap: performance.memory.usedJSHeapSize,
        timestamp: Date.now()
      });
    }
  }, 10000);

  // Send aggregated data on page unload
  window.addEventListener('beforeunload', () => {
    navigator.sendBeacon('/api/performance', JSON.stringify(perfData));
  });

  function sendToAnalytics(event, data) {
    // Your analytics implementation
    if (typeof gtag !== 'undefined') {
      gtag('event', event, data);
    }
  }
})();

Quick Diagnosis Checklist

When app freezes/hangs:

  1. Check Console: Any errors or warnings?
  2. Check Network Tab: Pending requests?
  3. Check Performance Tab: Recording shows long tasks?
  4. Check Memory Tab: Heap size growing?
  5. Framework DevTools:
    • Angular: Check Zone.js tasks
    • React: Check component renders
    • Vue: Check watcher count
  6. Take Heap Snapshot: Compare before/after
  7. Record CPU Profile: Identify hot functions
  8. Check Event Listeners: Growing count?
  9. Inspect DOM: Node count excessive?
  10. Test in Incognito: Extensions causing issues?

Best Practices

  1. Always clean up:

    • Remove event listeners
    • Cancel timers/intervals
    • Unsubscribe from observables
    • Clear WeakMaps/WeakSets
  2. Optimize renders:

    • Use React.memo/useMemo
    • Use Angular OnPush strategy
    • Use Vue computed properties
  3. Defer work:

    • Use requestIdleCallback
    • Use Web Workers
    • Implement virtual scrolling
  4. Monitor production:

    • Add performance monitoring
    • Track key metrics
    • Set up alerts
  5. Test performance:

    • Use Lighthouse CI
    • Add performance budgets
    • Test on slow devices