# Performance Analysis Guide

Comprehensive guide to analyzing web performance using Chrome DevTools Protocol, Puppeteer, and chrome-devtools skill.

## Table of Contents

- [Core Web Vitals](#core-web-vitals)
- [Performance Tracing](#performance-tracing)
- [Network Analysis](#network-analysis)
- [JavaScript Performance](#javascript-performance)
- [Rendering Performance](#rendering-performance)
- [Memory Analysis](#memory-analysis)
- [Optimization Strategies](#optimization-strategies)

---

## Core Web Vitals

### Overview

Core Web Vitals are Google's standardized metrics for measuring user experience:

- **LCP (Largest Contentful Paint)** - Loading performance (< 2.5s good)
- **FID (First Input Delay)** - Interactivity (< 100ms good)
- **CLS (Cumulative Layout Shift)** - Visual stability (< 0.1 good)

### Measuring with chrome-devtools-mcp

```javascript
// Start performance trace
await useTool("performance_start_trace", {
  categories: ["loading", "rendering", "scripting"],
});

// Navigate to page
await useTool("navigate_page", {
  url: "https://example.com",
});

// Wait for complete load
await useTool("wait_for", {
  waitUntil: "networkidle",
});

// Stop trace and get data
await useTool("performance_stop_trace");

// Get AI-powered insights
const insights = await useTool("performance_analyze_insight");

// insights will include:
// - LCP timing
// - FID analysis
// - CLS score
// - Performance recommendations
```

### Measuring with Puppeteer

```javascript
import puppeteer from "puppeteer";

const browser = await puppeteer.launch();
const page = await browser.newPage();

// Measure Core Web Vitals
await page.goto("https://example.com", {
  waitUntil: "networkidle2",
});

const vitals = await page.evaluate(() => {
  return new Promise((resolve) => {
    const vitals = {
      LCP: null,
      FID: null,
      CLS: 0,
    };

    // LCP
    new PerformanceObserver((list) => {
      const entries = list.getEntries();
      vitals.LCP =
        entries[entries.length - 1].renderTime ||
        entries[entries.length - 1].loadTime;
    }).observe({ entryTypes: ["largest-contentful-paint"] });

    // FID
    new PerformanceObserver((list) => {
      vitals.FID =
        list.getEntries()[0].processingStart - list.getEntries()[0].startTime;
    }).observe({ entryTypes: ["first-input"] });

    // CLS
    new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (!entry.hadRecentInput) {
          vitals.CLS += entry.value;
        }
      });
    }).observe({ entryTypes: ["layout-shift"] });

    // Wait 5 seconds for metrics
    setTimeout(() => resolve(vitals), 5000);
  });
});

console.log("Core Web Vitals:", vitals);
```

### Other Important Metrics

**TTFB (Time to First Byte)**

```javascript
const ttfb = await page.evaluate(() => {
  const [navigationEntry] = performance.getEntriesByType("navigation");
  return navigationEntry.responseStart - navigationEntry.requestStart;
});
```

**FCP (First Contentful Paint)**

```javascript
const fcp = await page.evaluate(() => {
  const paintEntries = performance.getEntriesByType("paint");
  const fcpEntry = paintEntries.find(
    (e) => e.name === "first-contentful-paint",
  );
  return fcpEntry ? fcpEntry.startTime : null;
});
```

**TTI (Time to Interactive)**

```javascript
// Requires lighthouse or manual calculation
const tti = await page.evaluate(() => {
  // Complex calculation based on network idle and long tasks
  // Best to use Lighthouse for accurate TTI
});
```

---

## Performance Tracing

### Chrome Trace Categories

**Loading:**

- Page load events
- Resource loading
- Parser activity

**Rendering:**

- Layout calculations
- Paint operations
- Compositing

**Scripting:**

- JavaScript execution
- V8 compilation
- Garbage collection

**Network:**

- HTTP requests
- WebSocket traffic
- Resource fetching

**Input:**

- User input processing
- Touch/scroll events

**GPU:**

- GPU operations
- Compositing work

### Record Performance Trace

**Using chrome-devtools-mcp:**

```javascript
// Start trace with specific categories
await useTool("performance_start_trace", {
  categories: ["loading", "rendering", "scripting", "network"],
});

// Perform actions
await useTool("navigate_page", { url: "https://example.com" });
await useTool("wait_for", { waitUntil: "networkidle" });

// Optional: Interact with page
await useTool("click", { uid: "button-uid" });

// Stop trace
const traceData = await useTool("performance_stop_trace");

// Analyze trace
const insights = await useTool("performance_analyze_insight");
```

**Using Puppeteer:**

```javascript
// Start tracing
await page.tracing.start({
  path: "trace.json",
  categories: [
    "devtools.timeline",
    "disabled-by-default-devtools.timeline",
    "disabled-by-default-v8.cpu_profiler",
  ],
});

// Navigate
await page.goto("https://example.com", {
  waitUntil: "networkidle2",
});

// Stop tracing
await page.tracing.stop();

// Analyze in Chrome DevTools (chrome://tracing)
```

### Analyze Trace Data

**Key Metrics from Trace:**

1. **Main Thread Activity**
   - JavaScript execution time
   - Layout/reflow time
   - Paint time
   - Long tasks (> 50ms)

2. **Network Waterfall**
   - Request start times
   - DNS lookup
   - Connection time
   - Download time

3. **Rendering Pipeline**
   - DOM construction
   - Style calculation
   - Layout
   - Paint
   - Composite

**Common Issues to Look For:**

- Long tasks blocking main thread
- Excessive JavaScript execution
- Layout thrashing
- Unnecessary repaints
- Slow network requests
- Large bundle sizes

---

## Network Analysis

### Monitor Network Requests

**Using chrome-devtools-mcp:**

```javascript
// Navigate to page
await useTool("navigate_page", { url: "https://example.com" });

// Wait for all requests
await useTool("wait_for", { waitUntil: "networkidle" });

// List all requests
const requests = await useTool("list_network_requests", {
  resourceTypes: ["Document", "Script", "Stylesheet", "Image", "XHR", "Fetch"],
  pageSize: 100,
});

// Analyze specific request
for (const req of requests.requests) {
  const details = await useTool("get_network_request", {
    requestId: req.id,
  });

  console.log({
    url: details.url,
    method: details.method,
    status: details.status,
    size: details.encodedDataLength,
    time: details.timing.receiveHeadersEnd - details.timing.requestTime,
    cached: details.fromCache,
  });
}
```

**Using Puppeteer:**

```javascript
const requests = [];

// Capture all requests
page.on("request", (request) => {
  requests.push({
    url: request.url(),
    method: request.method(),
    resourceType: request.resourceType(),
    headers: request.headers(),
  });
});

// Capture responses
page.on("response", (response) => {
  const request = response.request();
  console.log({
    url: response.url(),
    status: response.status(),
    size: response.headers()["content-length"],
    cached: response.fromCache(),
    timing: response.timing(),
  });
});

await page.goto("https://example.com");
```

### Network Performance Metrics

**Calculate Total Page Weight:**

```javascript
let totalBytes = 0;
let resourceCounts = {};

page.on("response", async (response) => {
  const type = response.request().resourceType();
  const buffer = await response.buffer();

  totalBytes += buffer.length;
  resourceCounts[type] = (resourceCounts[type] || 0) + 1;
});

await page.goto("https://example.com");

console.log("Total size:", (totalBytes / 1024 / 1024).toFixed(2), "MB");
console.log("Resources:", resourceCounts);
```

**Identify Slow Requests:**

```javascript
page.on("response", (response) => {
  const timing = response.timing();
  const totalTime = timing.receiveHeadersEnd - timing.requestTime;

  if (totalTime > 1000) {
    // Slower than 1 second
    console.log("Slow request:", {
      url: response.url(),
      time: totalTime.toFixed(2) + "ms",
      size: response.headers()["content-length"],
    });
  }
});
```

### Network Throttling

**Simulate Slow Connection:**

```javascript
// Using chrome-devtools-mcp
await useTool("emulate_network", {
  throttlingOption: "Slow 3G", // or 'Fast 3G', 'Slow 4G'
});

// Using Puppeteer
const client = await page.createCDPSession();
await client.send("Network.emulateNetworkConditions", {
  offline: false,
  downloadThroughput: (400 * 1024) / 8, // 400 Kbps
  uploadThroughput: (400 * 1024) / 8,
  latency: 2000, // 2000ms RTT
});
```

---

## JavaScript Performance

### Identify Long Tasks

**Using Performance Observer:**

```javascript
await page.evaluate(() => {
  return new Promise((resolve) => {
    const longTasks = [];

    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        longTasks.push({
          name: entry.name,
          duration: entry.duration,
          startTime: entry.startTime,
        });
      });
    });

    observer.observe({ entryTypes: ["longtask"] });

    // Collect for 10 seconds
    setTimeout(() => {
      observer.disconnect();
      resolve(longTasks);
    }, 10000);
  });
});
```

### CPU Profiling

**Using Puppeteer:**

```javascript
// Start CPU profiling
const client = await page.createCDPSession();
await client.send("Profiler.enable");
await client.send("Profiler.start");

// Navigate and interact
await page.goto("https://example.com");
await page.click(".button");

// Stop profiling
const { profile } = await client.send("Profiler.stop");

// Analyze profile (flame graph data)
// Import into Chrome DevTools for visualization
```

### JavaScript Coverage

**Identify Unused Code:**

```javascript
// Start coverage
await Promise.all([
  page.coverage.startJSCoverage(),
  page.coverage.startCSSCoverage(),
]);

// Navigate
await page.goto("https://example.com");

// Stop coverage
const [jsCoverage, cssCoverage] = await Promise.all([
  page.coverage.stopJSCoverage(),
  page.coverage.stopCSSCoverage(),
]);

// Calculate unused bytes
function calculateUnusedBytes(coverage) {
  let usedBytes = 0;
  let totalBytes = 0;

  for (const entry of coverage) {
    totalBytes += entry.text.length;
    for (const range of entry.ranges) {
      usedBytes += range.end - range.start - 1;
    }
  }

  return {
    usedBytes,
    totalBytes,
    unusedBytes: totalBytes - usedBytes,
    unusedPercentage: (((totalBytes - usedBytes) / totalBytes) * 100).toFixed(
      2,
    ),
  };
}

console.log("JS Coverage:", calculateUnusedBytes(jsCoverage));
console.log("CSS Coverage:", calculateUnusedBytes(cssCoverage));
```

### Bundle Size Analysis

**Analyze JavaScript Bundles:**

```javascript
page.on("response", async (response) => {
  const url = response.url();
  const type = response.request().resourceType();

  if (type === "script") {
    const buffer = await response.buffer();
    const size = buffer.length;

    console.log({
      url: url.split("/").pop(),
      size: (size / 1024).toFixed(2) + " KB",
      gzipped: response.headers()["content-encoding"] === "gzip",
    });
  }
});
```

---

## Rendering Performance

### Layout Thrashing Detection

**Monitor Layout Recalculations:**

```javascript
// Using Performance Observer
await page.evaluate(() => {
  return new Promise((resolve) => {
    const measurements = [];

    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (entry.entryType === "measure" && entry.name.includes("layout")) {
          measurements.push({
            name: entry.name,
            duration: entry.duration,
            startTime: entry.startTime,
          });
        }
      });
    });

    observer.observe({ entryTypes: ["measure"] });

    setTimeout(() => {
      observer.disconnect();
      resolve(measurements);
    }, 5000);
  });
});
```

### Paint and Composite Metrics

**Get Paint Metrics:**

```javascript
const paintMetrics = await page.evaluate(() => {
  const paints = performance.getEntriesByType("paint");
  return {
    firstPaint: paints.find((p) => p.name === "first-paint")?.startTime,
    firstContentfulPaint: paints.find(
      (p) => p.name === "first-contentful-paint",
    )?.startTime,
  };
});
```

### Frame Rate Analysis

**Monitor FPS:**

```javascript
await page.evaluate(() => {
  return new Promise((resolve) => {
    let frames = 0;
    let lastTime = performance.now();

    function countFrames() {
      frames++;
      requestAnimationFrame(countFrames);
    }

    countFrames();

    setTimeout(() => {
      const now = performance.now();
      const elapsed = (now - lastTime) / 1000;
      const fps = frames / elapsed;
      resolve(fps);
    }, 5000);
  });
});
```

### Layout Shifts (CLS)

**Track Individual Shifts:**

```javascript
await page.evaluate(() => {
  return new Promise((resolve) => {
    const shifts = [];
    let totalCLS = 0;

    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (!entry.hadRecentInput) {
          totalCLS += entry.value;
          shifts.push({
            value: entry.value,
            time: entry.startTime,
            elements: entry.sources?.map((s) => s.node),
          });
        }
      });
    });

    observer.observe({ entryTypes: ["layout-shift"] });

    setTimeout(() => {
      observer.disconnect();
      resolve({ totalCLS, shifts });
    }, 10000);
  });
});
```

---

## Memory Analysis

### Memory Metrics

**Get Memory Usage:**

```javascript
// Using chrome-devtools-mcp
await useTool("evaluate_script", {
  expression: `
    ({
      usedJSHeapSize: performance.memory?.usedJSHeapSize,
      totalJSHeapSize: performance.memory?.totalJSHeapSize,
      jsHeapSizeLimit: performance.memory?.jsHeapSizeLimit
    })
  `,
  returnByValue: true,
});

// Using Puppeteer
const metrics = await page.metrics();
console.log({
  jsHeapUsed: (metrics.JSHeapUsedSize / 1024 / 1024).toFixed(2) + " MB",
  jsHeapTotal: (metrics.JSHeapTotalSize / 1024 / 1024).toFixed(2) + " MB",
  domNodes: metrics.Nodes,
  documents: metrics.Documents,
  jsEventListeners: metrics.JSEventListeners,
});
```

### Memory Leak Detection

**Monitor Memory Over Time:**

```javascript
async function detectMemoryLeak(page, duration = 30000) {
  const samples = [];
  const interval = 1000; // Sample every second
  const samples_count = duration / interval;

  for (let i = 0; i < samples_count; i++) {
    const metrics = await page.metrics();
    samples.push({
      time: i,
      heapUsed: metrics.JSHeapUsedSize,
    });

    await page.waitForTimeout(interval);
  }

  // Analyze trend
  const firstSample = samples[0].heapUsed;
  const lastSample = samples[samples.length - 1].heapUsed;
  const increase = (((lastSample - firstSample) / firstSample) * 100).toFixed(
    2,
  );

  return {
    samples,
    memoryIncrease: increase + "%",
    possibleLeak: increase > 50, // > 50% increase indicates possible leak
  };
}

const leakAnalysis = await detectMemoryLeak(page, 30000);
console.log("Memory Analysis:", leakAnalysis);
```

### Heap Snapshot

**Capture Heap Snapshot:**

```javascript
const client = await page.createCDPSession();

// Take snapshot
await client.send("HeapProfiler.enable");
const { result } = await client.send("HeapProfiler.takeHeapSnapshot");

// Snapshot is streamed in chunks
// Save to file or analyze programmatically
```

---

## Optimization Strategies

### Image Optimization

**Detect Unoptimized Images:**

```javascript
const images = await page.evaluate(() => {
  const images = Array.from(document.querySelectorAll("img"));
  return images.map((img) => ({
    src: img.src,
    naturalWidth: img.naturalWidth,
    naturalHeight: img.naturalHeight,
    displayWidth: img.width,
    displayHeight: img.height,
    oversized:
      img.naturalWidth > img.width * 1.5 ||
      img.naturalHeight > img.height * 1.5,
  }));
});

const oversizedImages = images.filter((img) => img.oversized);
console.log("Oversized images:", oversizedImages);
```

### Font Loading

**Detect Render-Blocking Fonts:**

```javascript
const fonts = await page.evaluate(() => {
  return Array.from(document.fonts).map((font) => ({
    family: font.family,
    weight: font.weight,
    style: font.style,
    status: font.status,
    loaded: font.status === "loaded",
  }));
});

console.log("Fonts:", fonts);
```

### Third-Party Scripts

**Measure Third-Party Impact:**

```javascript
const thirdPartyDomains = [
  "googletagmanager.com",
  "facebook.net",
  "doubleclick.net",
];

page.on("response", async (response) => {
  const url = response.url();
  const isThirdParty = thirdPartyDomains.some((domain) => url.includes(domain));

  if (isThirdParty) {
    const buffer = await response.buffer();
    console.log({
      url: url,
      size: (buffer.length / 1024).toFixed(2) + " KB",
      type: response.request().resourceType(),
    });
  }
});
```

### Critical Rendering Path

**Identify Render-Blocking Resources:**

```javascript
await page.goto("https://example.com");

const renderBlockingResources = await page.evaluate(() => {
  const resources = performance.getEntriesByType("resource");
  return resources
    .filter((resource) => {
      return (
        (resource.initiatorType === "link" && resource.name.includes(".css")) ||
        (resource.initiatorType === "script" &&
          !resource.name.includes("async"))
      );
    })
    .map((r) => ({
      url: r.name,
      duration: r.duration,
      startTime: r.startTime,
    }));
});

console.log("Render-blocking resources:", renderBlockingResources);
```

### Lighthouse Integration

**Run Lighthouse Audit:**

```javascript
import lighthouse from "lighthouse";
import { launch } from "chrome-launcher";

// Launch Chrome
const chrome = await launch({ chromeFlags: ["--headless"] });

// Run Lighthouse
const { lhr } = await lighthouse("https://example.com", {
  port: chrome.port,
  onlyCategories: ["performance"],
});

// Get scores
console.log({
  performanceScore: lhr.categories.performance.score * 100,
  metrics: {
    FCP: lhr.audits["first-contentful-paint"].displayValue,
    LCP: lhr.audits["largest-contentful-paint"].displayValue,
    TBT: lhr.audits["total-blocking-time"].displayValue,
    CLS: lhr.audits["cumulative-layout-shift"].displayValue,
    SI: lhr.audits["speed-index"].displayValue,
  },
  opportunities: lhr.audits["opportunities"],
});

await chrome.kill();
```

---

## Performance Budgets

### Set Performance Budgets

```javascript
const budgets = {
  // Core Web Vitals
  LCP: 2500, // ms
  FID: 100, // ms
  CLS: 0.1, // score

  // Other metrics
  FCP: 1800, // ms
  TTI: 3800, // ms
  TBT: 300, // ms

  // Resource budgets
  totalPageSize: 2 * 1024 * 1024, // 2 MB
  jsSize: 500 * 1024, // 500 KB
  cssSize: 100 * 1024, // 100 KB
  imageSize: 1 * 1024 * 1024, // 1 MB

  // Request counts
  totalRequests: 50,
  jsRequests: 10,
  cssRequests: 5,
};

async function checkBudgets(page, budgets) {
  // Measure actual values
  const vitals = await measureCoreWebVitals(page);
  const resources = await analyzeResources(page);

  // Compare against budgets
  const violations = [];

  if (vitals.LCP > budgets.LCP) {
    violations.push(`LCP: ${vitals.LCP}ms exceeds budget of ${budgets.LCP}ms`);
  }

  if (resources.totalSize > budgets.totalPageSize) {
    violations.push(
      `Page size: ${resources.totalSize} exceeds budget of ${budgets.totalPageSize}`,
    );
  }

  // ... check other budgets

  return {
    passed: violations.length === 0,
    violations,
  };
}
```

---

## Automated Performance Testing

### CI/CD Integration

```javascript
// performance-test.js
import puppeteer from "puppeteer";

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

  // Measure metrics
  await page.goto(url, { waitUntil: "networkidle2" });
  const metrics = await page.metrics();
  const vitals = await measureCoreWebVitals(page);

  await browser.close();

  // Check against thresholds
  const thresholds = {
    LCP: 2500,
    FID: 100,
    CLS: 0.1,
    jsHeapSize: 50 * 1024 * 1024, // 50 MB
  };

  const failed = [];
  if (vitals.LCP > thresholds.LCP) failed.push("LCP");
  if (vitals.FID > thresholds.FID) failed.push("FID");
  if (vitals.CLS > thresholds.CLS) failed.push("CLS");
  if (metrics.JSHeapUsedSize > thresholds.jsHeapSize) failed.push("Memory");

  if (failed.length > 0) {
    console.error("Performance test failed:", failed);
    process.exit(1);
  }

  console.log("Performance test passed");
}

performanceTest(process.env.TEST_URL);
```

---

## Best Practices

### Performance Testing Checklist

1. **Measure Multiple Times**
   - Run tests 3-5 times
   - Use median values
   - Account for variance

2. **Test Different Conditions**
   - Fast 3G
   - Slow 3G
   - Offline
   - CPU throttling

3. **Test Different Devices**
   - Mobile (low-end)
   - Mobile (high-end)
   - Desktop
   - Tablet

4. **Monitor Over Time**
   - Track metrics in CI/CD
   - Set up alerts for regressions
   - Create performance dashboards

5. **Focus on User Experience**
   - Prioritize Core Web Vitals
   - Test real user journeys
   - Consider perceived performance

6. **Optimize Critical Path**
   - Minimize render-blocking resources
   - Defer non-critical JavaScript
   - Optimize font loading
   - Lazy load images

---

## Resources

- [Web.dev Performance](https://web.dev/performance/)
- [Chrome DevTools Performance](https://developer.chrome.com/docs/devtools/performance/)
- [Core Web Vitals](https://web.dev/vitals/)
- [Lighthouse](https://developer.chrome.com/docs/lighthouse/)
- [WebPageTest](https://www.webpagetest.org/)
