Skip to content

自定义性能监控

第一部分:监控系统设计

1.1 监控架构

javascript
// 完整的性能监控系统架构
class PerformanceMonitor {
  constructor(config = {}) {
    this.config = {
      endpoint: '/api/performance',
      sampleRate: 0.1,  // 采样率10%
      batchSize: 10,
      batchInterval: 5000,
      ...config
    };
    
    this.metrics = [];
    this.batchTimer = null;
    this.init();
  }
  
  init() {
    this.trackWebVitals();
    this.trackCustomMetrics();
    this.trackErrors();
    this.trackResources();
    this.startBatching();
  }
  
  trackWebVitals() {
    import('web-vitals').then(({ onCLS, onFID, onLCP, onFCP, onTTFB }) => {
      onLCP(this.handleMetric.bind(this));
      onFID(this.handleMetric.bind(this));
      onCLS(this.handleMetric.bind(this));
      onFCP(this.handleMetric.bind(this));
      onTTFB(this.handleMetric.bind(this));
    });
  }
  
  trackCustomMetrics() {
    // 自定义业务指标
    this.trackComponentRender();
    this.trackAPIRequests();
    this.trackUserInteractions();
  }
  
  trackErrors() {
    window.addEventListener('error', (event) => {
      this.record({
        type: 'error',
        message: event.message,
        filename: event.filename,
        line: event.lineno,
        column: event.colno
      });
    });
    
    window.addEventListener('unhandledrejection', (event) => {
      this.record({
        type: 'unhandled-rejection',
        reason: event.reason
      });
    });
  }
  
  trackResources() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
          this.record({
            type: 'resource',
            name: entry.name,
            duration: entry.duration,
            size: entry.transferSize
          });
        }
      }
    });
    
    observer.observe({ entryTypes: ['resource'] });
  }
  
  handleMetric(metric) {
    if (Math.random() > this.config.sampleRate) {
      return;  // 采样
    }
    
    this.record({
      type: 'web-vital',
      name: metric.name,
      value: metric.value,
      rating: metric.rating,
      delta: metric.delta
    });
  }
  
  record(data) {
    this.metrics.push({
      ...data,
      timestamp: Date.now(),
      url: window.location.href,
      userAgent: navigator.userAgent
    });
    
    if (this.metrics.length >= this.config.batchSize) {
      this.flush();
    }
  }
  
  startBatching() {
    this.batchTimer = setInterval(() => {
      if (this.metrics.length > 0) {
        this.flush();
      }
    }, this.config.batchInterval);
  }
  
  flush() {
    if (this.metrics.length === 0) return;
    
    const data = [...this.metrics];
    this.metrics = [];
    
    if (navigator.sendBeacon) {
      navigator.sendBeacon(
        this.config.endpoint,
        JSON.stringify(data)
      );
    } else {
      fetch(this.config.endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
        keepalive: true
      });
    }
  }
  
  trackComponentRender() {
    // React Profiler集成
  }
  
  trackAPIRequests() {
    // API请求监控
  }
  
  trackUserInteractions() {
    // 用户交互追踪
  }
}

// 使用
const monitor = new PerformanceMonitor({
  endpoint: '/api/metrics',
  sampleRate: 0.2
});

1.2 React集成

javascript
// React性能监控组件
function PerformanceProvider({ children }) {
  const metricsRef = useRef([]);
  
  useEffect(() => {
    // 监控Web Vitals
    import('web-vitals').then(({ onCLS, onFID, onLCP }) => {
      onLCP((metric) => recordMetric('LCP', metric));
      onFID((metric) => recordMetric('FID', metric));
      onCLS((metric) => recordMetric('CLS', metric));
    });
    
    // 页面卸载时发送数据
    const handleUnload = () => {
      if (metricsRef.current.length > 0) {
        sendMetrics(metricsRef.current);
      }
    };
    
    window.addEventListener('beforeunload', handleUnload);
    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'hidden') {
        handleUnload();
      }
    });
    
    return () => {
      window.removeEventListener('beforeunload', handleUnload);
    };
  }, []);
  
  const recordMetric = (name, metric) => {
    metricsRef.current.push({
      name,
      value: metric.value,
      rating: metric.rating,
      timestamp: Date.now()
    });
  };
  
  const sendMetrics = (metrics) => {
    navigator.sendBeacon(
      '/api/performance',
      JSON.stringify(metrics)
    );
  };
  
  return children;
}

// 使用
function App() {
  return (
    <PerformanceProvider>
      <MyApp />
    </PerformanceProvider>
  );
}

1.3 自定义指标

javascript
// 业务关键指标
function useBusinessMetrics() {
  useEffect(() => {
    // 首次有意义的绘制
    performance.mark('meaningful-paint');
    
    // 用户可交互时间
    window.addEventListener('load', () => {
      performance.mark('interactive');
      
      performance.measure(
        'time-to-interactive',
        'navigationStart',
        'interactive'
      );
    });
    
    // 自定义业务指标
    trackCheckoutFlow();
    trackSearchPerformance();
    trackVideoPlayback();
  }, []);
}

// 结账流程监控
function trackCheckoutFlow() {
  performance.mark('checkout-start');
  
  // 各步骤标记
  const steps = [
    'cart-loaded',
    'shipping-entered',
    'payment-entered',
    'order-submitted'
  ];
  
  steps.forEach(step => {
    document.addEventListener(`checkout:${step}`, () => {
      performance.mark(step);
      performance.measure(
        `checkout-${step}`,
        'checkout-start',
        step
      );
    });
  });
}

// 搜索性能监控
function useSearchMetrics() {
  const trackSearch = useCallback((query, results, duration) => {
    const metric = {
      type: 'search',
      query,
      resultCount: results.length,
      duration,
      timestamp: Date.now()
    };
    
    sendMetric(metric);
    
    // 慢速搜索告警
    if (duration > 1000) {
      console.warn('Slow search:', query, duration);
    }
  }, []);
  
  return trackSearch;
}

// 使用
function SearchComponent() {
  const trackSearch = useSearchMetrics();
  
  const handleSearch = async (query) => {
    const startTime = performance.now();
    const results = await performSearch(query);
    const duration = performance.now() - startTime;
    
    trackSearch(query, results, duration);
    
    return results;
  };
}

第二部分:数据采集

2.1 Navigation Timing

javascript
// Navigation Timing API
function collectNavigationTiming() {
  const perfData = performance.getEntriesByType('navigation')[0];
  
  if (!perfData) return null;
  
  return {
    // DNS查询时间
    dnsTime: perfData.domainLookupEnd - perfData.domainLookupStart,
    
    // TCP连接时间
    tcpTime: perfData.connectEnd - perfData.connectStart,
    
    // TLS协商时间
    tlsTime: perfData.secureConnectionStart > 0
      ? perfData.connectEnd - perfData.secureConnectionStart
      : 0,
    
    // 请求响应时间
    requestTime: perfData.responseStart - perfData.requestStart,
    
    // 响应时间
    responseTime: perfData.responseEnd - perfData.responseStart,
    
    // DOM处理时间
    domTime: perfData.domComplete - perfData.domInteractive,
    
    // 页面加载总时间
    loadTime: perfData.loadEventEnd - perfData.fetchStart,
    
    // DOMContentLoaded时间
    domContentLoadedTime: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
    
    // 首字节时间
    ttfb: perfData.responseStart - perfData.requestStart
  };
}

// React Hook
function useNavigationTiming() {
  const [timing, setTiming] = useState(null);
  
  useEffect(() => {
    window.addEventListener('load', () => {
      setTimeout(() => {
        const navigationTiming = collectNavigationTiming();
        setTiming(navigationTiming);
        
        // 上报数据
        sendMetric('navigation-timing', navigationTiming);
      }, 0);
    });
  }, []);
  
  return timing;
}

2.2 Resource Timing

javascript
// Resource Timing API
function collectResourceTiming() {
  const resources = performance.getEntriesByType('resource');
  
  const resourceMetrics = resources.map(resource => ({
    name: resource.name,
    type: resource.initiatorType,
    duration: resource.duration,
    size: resource.transferSize,
    cached: resource.transferSize === 0
  }));
  
  // 分类统计
  const stats = {
    scripts: resourceMetrics.filter(r => r.type === 'script'),
    styles: resourceMetrics.filter(r => r.type === 'link' || r.type === 'css'),
    images: resourceMetrics.filter(r => r.type === 'img'),
    fetch: resourceMetrics.filter(r => r.type === 'fetch'),
    other: resourceMetrics.filter(r => 
      !['script', 'link', 'css', 'img', 'fetch'].includes(r.type)
    )
  };
  
  return {
    resources: resourceMetrics,
    stats,
    totalSize: resourceMetrics.reduce((sum, r) => sum + r.size, 0),
    totalDuration: resourceMetrics.reduce((sum, r) => sum + r.duration, 0)
  };
}

// 监控慢速资源
function monitorSlowResources() {
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (entry.duration > 1000) {  // >1s的资源
        console.warn('Slow resource:', {
          name: entry.name,
          duration: entry.duration,
          size: entry.transferSize
        });
        
        sendAlert('slow-resource', {
          url: entry.name,
          duration: entry.duration
        });
      }
    }
  });
  
  observer.observe({ entryTypes: ['resource'] });
}

2.3 User Timing

javascript
// User Timing API
function usePerformanceTiming() {
  const mark = useCallback((name) => {
    performance.mark(name);
  }, []);
  
  const measure = useCallback((name, startMark, endMark) => {
    performance.measure(name, startMark, endMark);
    
    const measure = performance.getEntriesByName(name, 'measure').pop();
    
    if (measure) {
      sendMetric('custom-timing', {
        name,
        duration: measure.duration,
        startTime: measure.startTime
      });
    }
    
    return measure?.duration;
  }, []);
  
  const clear = useCallback(() => {
    performance.clearMarks();
    performance.clearMeasures();
  }, []);
  
  return { mark, measure, clear };
}

// 使用示例
function DataLoader() {
  const { mark, measure } = usePerformanceTiming();
  const [data, setData] = useState(null);
  
  const loadData = async () => {
    mark('data-fetch-start');
    
    const result = await fetch('/api/data');
    const json = await result.json();
    
    mark('data-fetch-end');
    const duration = measure('data-fetch', 'data-fetch-start', 'data-fetch-end');
    
    console.log('Data fetch took:', duration, 'ms');
    
    setData(json);
  };
  
  useEffect(() => {
    loadData();
  }, []);
  
  return data ? <Display data={data} /> : <Loading />;
}

// 组件渲染性能
function ComponentPerformance({ children, name }) {
  const renderCount = useRef(0);
  const { mark, measure } = usePerformanceTiming();
  
  const markStart = `${name}-render-${renderCount.current}-start`;
  const markEnd = `${name}-render-${renderCount.current}-end`;
  
  mark(markStart);
  
  useEffect(() => {
    mark(markEnd);
    const duration = measure(`${name}-render-${renderCount.current}`, markStart, markEnd);
    
    if (duration > 16) {  // >16ms (60fps阈值)
      console.warn(`${name} slow render:`, duration, 'ms');
    }
    
    renderCount.current++;
  });
  
  return children;
}

第三部分:实时监控

3.1 性能仪表板

javascript
// 实时性能仪表板
function PerformanceDashboard() {
  const [metrics, setMetrics] = useState({
    lcp: null,
    fid: null,
    cls: null,
    memoryUsage: null,
    fps: null
  });
  
  useEffect(() => {
    // Web Vitals
    onLCP((metric) => setMetrics(m => ({ ...m, lcp: metric.value })));
    onFID((metric) => setMetrics(m => ({ ...m, fid: metric.value })));
    onCLS((metric) => setMetrics(m => ({ ...m, cls: metric.value })));
    
    // 内存使用
    if (performance.memory) {
      const updateMemory = () => {
        setMetrics(m => ({
          ...m,
          memoryUsage: performance.memory.usedJSHeapSize / (1024 * 1024)
        }));
      };
      
      const memoryInterval = setInterval(updateMemory, 1000);
      return () => clearInterval(memoryInterval);
    }
    
    // FPS监控
    let lastTime = performance.now();
    let frames = 0;
    
    const measureFPS = () => {
      frames++;
      const currentTime = performance.now();
      
      if (currentTime >= lastTime + 1000) {
        setMetrics(m => ({ ...m, fps: frames }));
        frames = 0;
        lastTime = currentTime;
      }
      
      requestAnimationFrame(measureFPS);
    };
    
    requestAnimationFrame(measureFPS);
  }, []);
  
  return (
    <div className="performance-dashboard">
      <MetricCard title="LCP" value={metrics.lcp} unit="ms" threshold={2500} />
      <MetricCard title="FID" value={metrics.fid} unit="ms" threshold={100} />
      <MetricCard title="CLS" value={metrics.cls} unit="" threshold={0.1} />
      <MetricCard title="Memory" value={metrics.memoryUsage} unit="MB" />
      <MetricCard title="FPS" value={metrics.fps} unit="" threshold={60} />
    </div>
  );
}

function MetricCard({ title, value, unit, threshold }) {
  const status = threshold 
    ? (value <= threshold ? 'good' : 'poor')
    : 'neutral';
  
  return (
    <div className={`metric-card ${status}`}>
      <h3>{title}</h3>
      <div className="value">
        {value !== null ? value.toFixed(2) : '--'}
        {unit && <span className="unit">{unit}</span>}
      </div>
      {threshold && (
        <div className="threshold">
          目标: ≤{threshold}{unit}
        </div>
      )}
    </div>
  );
}

3.2 异常检测

javascript
// 性能异常检测
class PerformanceAnomalyDetector {
  constructor() {
    this.baseline = {};
    this.threshold = 1.5;  // 超过基线1.5倍视为异常
  }
  
  setBaseline(metric, value) {
    this.baseline[metric] = value;
  }
  
  check(metric, value) {
    const baseline = this.baseline[metric];
    
    if (!baseline) {
      this.setBaseline(metric, value);
      return false;
    }
    
    const ratio = value / baseline;
    
    if (ratio > this.threshold) {
      this.reportAnomaly(metric, value, baseline, ratio);
      return true;
    }
    
    return false;
  }
  
  reportAnomaly(metric, value, baseline, ratio) {
    console.warn('Performance anomaly detected:', {
      metric,
      current: value,
      baseline,
      ratio: ratio.toFixed(2)
    });
    
    // 发送告警
    fetch('/api/alerts', {
      method: 'POST',
      body: JSON.stringify({
        type: 'performance-anomaly',
        metric,
        value,
        baseline,
        timestamp: Date.now()
      })
    });
  }
}

// 使用
const detector = new PerformanceAnomalyDetector();

onLCP((metric) => {
  const isAnomaly = detector.check('LCP', metric.value);
  
  if (isAnomaly) {
    // 触发详细分析
    captureDetailedMetrics();
  }
});

3.3 趋势分析

javascript
// 性能趋势追踪
class PerformanceTrendTracker {
  constructor(windowSize = 100) {
    this.windowSize = windowSize;
    this.data = {
      lcp: [],
      fid: [],
      cls: []
    };
  }
  
  add(metric, value) {
    if (!this.data[metric]) {
      this.data[metric] = [];
    }
    
    this.data[metric].push({
      value,
      timestamp: Date.now()
    });
    
    // 保持窗口大小
    if (this.data[metric].length > this.windowSize) {
      this.data[metric].shift();
    }
  }
  
  getStats(metric) {
    const values = this.data[metric].map(d => d.value);
    
    if (values.length === 0) return null;
    
    const sorted = [...values].sort((a, b) => a - b);
    
    return {
      mean: values.reduce((a, b) => a + b) / values.length,
      median: sorted[Math.floor(sorted.length / 2)],
      p75: sorted[Math.floor(sorted.length * 0.75)],
      p95: sorted[Math.floor(sorted.length * 0.95)],
      min: sorted[0],
      max: sorted[sorted.length - 1]
    };
  }
  
  getTrend(metric, period = 10) {
    const recent = this.data[metric].slice(-period);
    
    if (recent.length < 2) return 'stable';
    
    const recentAvg = recent.reduce((sum, d) => sum + d.value, 0) / recent.length;
    const previousAvg = this.data[metric]
      .slice(-period * 2, -period)
      .reduce((sum, d) => sum + d.value, 0) / period;
    
    const change = (recentAvg - previousAvg) / previousAvg;
    
    if (change > 0.1) return 'degrading';
    if (change < -0.1) return 'improving';
    return 'stable';
  }
}

// 使用
const tracker = new PerformanceTrendTracker();

onLCP((metric) => {
  tracker.add('lcp', metric.value);
  
  const stats = tracker.getStats('lcp');
  const trend = tracker.getTrend('lcp');
  
  console.log('LCP stats:', stats);
  console.log('LCP trend:', trend);
});

注意事项

1. 采样策略

javascript
// 生产环境采样
const shouldSample = () => {
  // 10%采样
  if (Math.random() > 0.1) return false;
  
  // 排除爬虫
  const isBot = /bot|crawler|spider/i.test(navigator.userAgent);
  if (isBot) return false;
  
  return true;
};

if (shouldSample()) {
  setupPerformanceMonitoring();
}

2. 数据隐私

javascript
// 匿名化敏感信息
function sanitizeURL(url) {
  const parsed = new URL(url);
  
  // 移除查询参数中的敏感信息
  ['token', 'key', 'password'].forEach(param => {
    parsed.searchParams.delete(param);
  });
  
  return parsed.toString();
}

// 收集前处理
function collectMetric(metric) {
  return {
    ...metric,
    url: sanitizeURL(window.location.href),
    // 不收集用户标识
  };
}

3. 性能影响

javascript
// 监控本身不应影响性能
// 1. 异步发送数据
// 2. 使用sendBeacon
// 3. 批量上报
// 4. 采样收集

常见问题

Q1: 应该监控哪些指标?

A: Core Web Vitals + 业务关键指标。

Q2: 如何减少监控开销?

A: 采样、批量发送、异步处理。

Q3: 真实用户数据和实验室数据哪个重要?

A: 都重要,RUM反映真实情况,实验室数据便于复现。

Q4: 多久审查一次性能数据?

A: 建议每周审查,重大问题实时告警。

Q5: 如何设定告警阈值?

A: 基于历史数据和业务目标。

Q6: 性能监控影响隐私吗?

A: 需匿名化敏感信息,遵守隐私政策。

Q7: 如何处理性能数据?

A: 存储、聚合、可视化、分析、告警。

Q8: 移动端需要特殊处理吗?

A: 是的,网络和设备条件不同。

Q9: 如何验证监控系统有效性?

A: 模拟性能问题,检查是否正确检测和上报。

Q10: React 19的监控注意事项?

A: 注意并发特性和Suspense的性能模式。

总结

核心要点

1. 监控系统设计
   ✅ 全面的指标收集
   ✅ 实时数据处理
   ✅ 异常检测告警
   ✅ 趋势分析

2. 数据采集
   ✅ Web Vitals
   ✅ Navigation Timing
   ✅ Resource Timing
   ✅ 自定义指标

3. 实施要点
   ✅ 采样策略
   ✅ 批量上报
   ✅ 隐私保护
   ✅ 性能影响最小化

最佳实践

1. 监控策略
   ✅ 覆盖关键指标
   ✅ 真实用户监控
   ✅ 实验室测试补充
   ✅ 持续优化

2. 数据处理
   ✅ 实时聚合
   ✅ 趋势分析
   ✅ 异常告警
   ✅ 可视化展示

3. 优化闭环
   ✅ 监控发现问题
   ✅ 分析定位原因
   ✅ 优化解决问题
   ✅ 验证优化效果

自定义性能监控是持续优化的基础,建立完善的监控体系能及时发现和解决性能问题。