Performance API Deep Dive: Long Tasks, INP, and Performance Monitoring Systems

性能优化(Updated Jun 20, 2026)

Performance API Ecosystem Overview

The browser Performance API provides a complete set of performance measurement and monitoring interfaces, covering the full chain from navigation timing to element rendering, long task detection, and interaction responsiveness.

API Monitoring Target Type
PerformanceObserver Observer for all performance entries Core
PerformanceNavigationTiming Page navigation and loading Navigation
PerformanceResourceTiming Resource loading duration Resource
PerformanceLongTaskTiming Long tasks (>50ms) Interaction
PerformanceEventTiming User interaction latency Interaction
PerformanceElementTiming Element render timing Rendering
PerformanceLayoutShift Layout shifts Stability
PerformancePaintTiming First paint / first contentful paint Rendering

PerformanceObserver Basics

Core Pattern

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.startTime, entry.duration);
  }
});

observer.observe({ type: 'longtask', buffered: true });

Observing Multiple Types

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    switch (entry.entryType) {
      case 'longtask':
        reportLongTask(entry);
        break;
      case 'event':
        reportEventTiming(entry);
        break;
      case 'layout-shift':
        reportLayoutShift(entry);
        break;
      case 'element':
        reportElementTiming(entry);
        break;
    }
  }
});

observer.observe({
  type: ['longtask', 'event', 'layout-shift', 'element'],
  buffered: true
});

Long Tasks Monitoring

Long Tasks are tasks executing on the main thread for more than 50ms, blocking user interaction and causing jank.

Basic Monitoring

const longTaskObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log({
      name: entry.name,
      startTime: entry.startTime.toFixed(2),
      duration: entry.duration.toFixed(2),
      culprit: entry.attribution?.[0]?.containerType,
      containerName: entry.attribution?.[0]?.containerName,
    });
  }
});

longTaskObserver.observe({ type: 'longtask', buffered: true });

Long Task Attribution Analysis

function analyzeLongTask(entry) {
  const attribution = entry.attribution?.[0];
  if (!attribution) return { type: 'unknown' };

  return {
    type: attribution.containerType,
    containerName: attribution.containerName,
    containerId: attribution.containerId,
    containerSrc: attribution.containerSrc,
    duration: entry.duration,
    startTime: entry.startTime,
  };
}

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    const analysis = analyzeLongTask(entry);
    reportToAnalytics('longtask', analysis);
  }
});

observer.observe({ type: 'longtask', buffered: true });

Long Task Optimization Strategies

Strategy Description Example
Task splitting Break large tasks into <50ms chunks scheduler.yield()
Deferred execution Postpone non-critical tasks to idle periods requestIdleCallback
Web Workers Move CPU-intensive work off main thread Data processing, sorting
Code splitting Load JS on demand to reduce initial payload Dynamic import()
Scheduling priority Control execution order with Scheduler API scheduler.postTask
async function yieldToMain() {
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }
  return new Promise((resolve) => setTimeout(resolve, 0));
}

async function processLargeList(items) {
  const results = [];
  for (let i = 0; i < items.length; i++) {
    results.push(processItem(items[i]));
    if (i % 100 === 0) await yieldToMain();
  }
  return results;
}

Interaction to Next Paint (INP)

INP is a Core Web Vitals metric measuring the delay from user interaction to the next paint.

INP Rating Thresholds

Rating INP Value User Experience
Good ≤ 200ms Responsive
Needs improvement 200-500ms Perceivable delay
Poor > 500ms Noticeable jank

Monitoring INP

let maxInteraction = 0;
let worstInteraction = null;

const inpObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.interactionId) continue;

    const duration = entry.duration;
    if (duration > maxInteraction) {
      maxInteraction = duration;
      worstInteraction = {
        interactionId: entry.interactionId,
        type: entry.name,
        duration,
        startTime: entry.startTime,
        processingStart: entry.processingStart,
        processingEnd: entry.processingEnd,
        inputDelay: entry.processingStart - entry.startTime,
        processingDuration: entry.processingEnd - entry.processingStart,
        presentationDelay: entry.duration - (entry.processingEnd - entry.startTime),
      };
    }
  }
});

inpObserver.observe({ type: 'event', buffered: true, durationThreshold: 16 });

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    if (worstInteraction) {
      reportToAnalytics('inp', {
        value: maxInteraction,
        rating: maxInteraction <= 200 ? 'good' : maxInteraction <= 500 ? 'needs-improvement' : 'poor',
        ...worstInteraction,
      });
    }
  }
});

INP Latency Breakdown

User interaction → Input delay → Event processing → Presentation delay → Next frame paint
                 ────────────────────────────────────────────────────────
                 |←                  INP (duration)                    →|

Input delay: startTime → processingStart
  Cause: Main thread blocked by long tasks

Event processing: processingStart → processingEnd
  Cause: Slow event callback execution

Presentation delay: processingEnd → next frame paint
  Cause: Heavy rAF callbacks or style/layout computation

Element Timing Monitoring

Element Timing tracks the render time of critical content, applicable to LCP elements and custom key elements.

<img src="hero.jpg" elementtiming="hero-image" />
<p elementtiming="hero-text">Critical content</p>
<div elementtiming="main-content">...</div>
const elementObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log({
      identifier: entry.identifier,
      startTime: entry.startTime,
      renderTime: entry.renderTime,
      loadTime: entry.loadTime,
      size: entry.size,
      url: entry.url,
      elementType: entry.element?.tagName,
    });
  }
});

elementObserver.observe({ type: 'element', buffered: true });

Building a RUM Monitoring System

Core Metrics Collector

class PerformanceMonitor {
  constructor(options = {}) {
    this.endpoint = options.endpoint;
    this.metrics = {};
    this.observers = [];
  }

  start() {
    this.collectWebVitals();
    this.collectLongTasks();
    this.collectResourceTiming();
    this.collectNavigationTiming();
    this.scheduleReport();
  }

  collectWebVitals() {
    this.observe('event', (entry) => {
      if (!entry.interactionId) return;
      this.metrics.inp = Math.max(this.metrics.inp ?? 0, entry.duration);
    });

    this.observe('largest-contentful-paint', (entry) => {
      this.metrics.lcp = entry.startTime;
    });

    this.observe('layout-shift', (entry) => {
      if (!entry.hadRecentInput) {
        this.metrics.cls = (this.metrics.cls ?? 0) + entry.value;
      }
    });

    this.observe('first-input', (entry) => {
      this.metrics.fid = entry.processingStart - entry.startTime;
    });
  }

  collectLongTasks() {
    this.observe('longtask', (entry) => {
      if (!this.metrics.longTasks) this.metrics.longTasks = [];
      this.metrics.longTasks.push({
        duration: entry.duration,
        startTime: entry.startTime,
        culprit: entry.attribution?.[0]?.containerType,
      });
    });
  }

  collectResourceTiming() {
    this.observe('resource', (entry) => {
      if (entry.duration > 1000) {
        if (!this.metrics.slowResources) this.metrics.slowResources = [];
        this.metrics.slowResources.push({
          name: entry.name,
          duration: entry.duration,
          initiatorType: entry.initiatorType,
          transferSize: entry.transferSize,
        });
      }
    });
  }

  collectNavigationTiming() {
    const [nav] = performance.getEntriesByType('navigation');
    if (nav) {
      this.metrics.ttfb = nav.responseStart - nav.requestStart;
      this.metrics.domContentLoaded = nav.domContentLoadedEventEnd - nav.startTime;
      this.metrics.loadComplete = nav.loadEventEnd - nav.startTime;
      this.metrics.domInteractive = nav.domInteractive - nav.startTime;
    }
  }

  observe(type, callback) {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) callback(entry);
    });
    observer.observe({ type, buffered: true });
    this.observers.push(observer);
  }

  scheduleReport() {
    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'hidden') this.report();
    });
  }

  async report() {
    const payload = {
      url: location.href,
      timestamp: Date.now(),
      userAgent: navigator.userAgent,
      connection: navigator.connection
        ? { effectiveType: navigator.connection.effectiveType, downlink: navigator.connection.downlink }
        : null,
      metrics: this.metrics,
    };

    if (navigator.sendBeacon) {
      navigator.sendBeacon(this.endpoint, JSON.stringify(payload));
    } else {
      fetch(this.endpoint, { method: 'POST', body: JSON.stringify(payload), keepalive: true });
    }
  }

  destroy() {
    this.observers.forEach((o) => o.disconnect());
  }
}

const monitor = new PerformanceMonitor({ endpoint: '/api/vitals' });
monitor.start();

Core Web Vitals Overview

Metric Full Name Threshold (Good/Poor) Observer Type Weight
LCP Largest Contentful Paint ≤2.5s / >4s largest-contentful-paint 25%
INP Interaction to Next Paint ≤200ms / >500ms event 25%
CLS Cumulative Layout Shift ≤0.1 / >0.25 layout-shift 25%
FCP First Contentful Paint ≤1.8s / >3s paint 10%
TTFB Time to First Byte ≤800ms / >1800ms navigation 15%

Performance Data Reporting Optimization

Batched Reporting and Compression

class MetricsBatcher {
  constructor(endpoint, options = {}) {
    this.endpoint = endpoint;
    this.batchSize = options.batchSize ?? 10;
    this.flushInterval = options.flushInterval ?? 5000;
    this.queue = [];
    this.timer = null;
  }

  enqueue(metric) {
    this.queue.push(metric);
    if (this.queue.length >= this.batchSize) this.flush();
    else if (!this.timer) {
      this.timer = setTimeout(() => this.flush(), this.flushInterval);
    }
  }

  flush() {
    if (this.timer) { clearTimeout(this.timer); this.timer = null; }
    if (this.queue.length === 0) return;

    const payload = JSON.stringify(this.queue);
    this.queue = [];

    if (navigator.sendBeacon) {
      navigator.sendBeacon(this.endpoint, payload);
    } else {
      fetch(this.endpoint, { method: 'POST', body: payload, keepalive: true });
    }
  }
}

Best Practices Summary

  1. Use buffered: true: Ensure no early-page performance entries are missed
  2. Report INP on visibilitychange: Capture the worst interaction value per session
  3. Long task attribution: Use attribution to pinpoint the source (iframe/script)
  4. Prefer sendBeacon: Guarantee data delivery during page unload
  5. Address all three INP phases: Optimize input delay, event processing, and presentation delay separately

Try these browser-local tools — no sign-up required →

#Performance API#PerformanceObserver#Long Tasks#INP#性能监控