前端性能分析 " Performance API "

2,686 阅读3分钟
定义

Performance 接口可以获取到当前页面中与性能相关的信息。它是 High Resolution Time API 的一部分,同时也融合了 Performance Timeline APINavigation Timing APIUser Timing APIResource Timing API

注意:除了以下指出的情况外,该接口及其成员在 Web Worker 中可用。此外,还需注意,performance 的创建和衡量都是同一环境下的。即,如果你在主线程(或者其他 worker)中创建了一个 performance,那么它在另外的 worker 线程中是不可用的;反之亦然。

Performance常见名词解释
  1. FP (First Paint) 首次绘制
  2. FCP (First Contentful Paint) 首次内容绘制
  3. LCP (Largest Contentful Paint) 最大内容渲染
  4. DCL (DomContentloaded)
  5. FMP(First Meaningful Paint) 首次有效绘制
  6. L (onLoad)
  7. TTI (Time to Interactive) 可交互时间
  8. TBT (Total Blocking Time) 页面阻塞总时长
  9. FID (First Input Delay) 首次输入延迟
  10. CLS (Cumulative Layout Shift) 累积布局偏移
  11. SI (Speed Index)
Performance API
MDN Web docs描述
Resource Timing API获取和分析应用资源加载的详细网络计时数据
Navigation_timing_API提供了可用于衡量一个网站性能的数据
Performance支持应用程序中客户端的延时测量
Performance_Timeline提供了可用于衡量一个应用程序的客户端延时的数据
User_Timing_API允许开发者在浏览器性能时间线中创建针对特定应用的时间戳
Frame_Timing_API提供有关浏览器事件循环的帧计时数据
Network_Information_API可以获取到系统的网络连接信息,应用程序可以根据此信息为用户展现不同清晰度的内容
Navigation Timing: Processing Model
  1. Navigation Timing Level 1

timing-overview.png

  1. Navigation Timing Level 2

timestamp-diagram.svg

  • 上一个文档卸载
  • 重定向
  • 浏览器准备好使用http抓取文档
  • 检查本地缓存
  • 查询DNS域名
  • TCP建立连接
  • HTTP请求、响应
  • 渲染DOM树并解析
  • 网页开始加载资源
  • 准备就绪触发load事件执行回调函数

性能上报

DCA061F4-943D-4A4E-992F-7AE02E7D42AF.png

// onload事件触发 - 等待1s - 上报性能
var page = windows.performance.timing
// DNS
dns = page.domainLookupEnd - page.domainLookupStart
// TCP
tcp = page.connectEnd - page.connectStart
// 请求返回时长
connectTime = page.responseEnd - page.requestStart
// 服务端响应时长
responseTime = page.responseEnd - page.responseStart
// DOM分析时长
domAnalysis = page.domComplete - page.domInteractive
// Dom渲染时长
domReady = page.domContentLoadedEventEnd - page.navigationStart
// 页面加载所需的总时长
loadTime = page.loadEventEnd - page.navigationStart
PerformanceNavigation (将废弃)

属性

  1. redirectCount: 如果有重定向的话,页面通过几次重定向跳转而来
  2. type
type描述
0即 TYPE_NAVIGATENEXT 正常进入的页面(非刷新、非重定向等)
1即 TYPE_RELOAD 通过 window.location.reload() 刷新的页面
2即 TYPE_BACK_FORWARD 通过浏览器的前进后退按钮进入的页面(历史记录)
255即 TYPE_UNDEFINED 非以上方式进入的页面
方法
  1. Performance.getEntries()
  2. Performance.getEntriesByType()
  3. Performance.getEntriesByName()
  4. performance.now() 精准计算程序运行时间
mark、measures 计算程序时间
  1. performance.mark() 标记各种时间戳,进行打点
  2. performance.measure() 测量
  3. performance.clearMarks() 清除打点
  4. performance.clearMeasures() 清除测量数据
function randomFunc (n) {  
    if (!n) {
        // 生成一个随机数
        n = ~~(Math.random() * 10000);
    }
    var nameStart = 'markStart' + n; 
    var nameEnd   = 'markEnd' + n; 
    // 函数执行前做个标记
    window.performance.mark(nameStart);
 
    for (var i = 0; i < n; i++) {
        // do nothing
    }
 
    // 函数执行后再做个标记
    window.performance.mark(nameEnd);
 
    // 然后测量这个两个标记间的时间距离,并保存起来
    var name = 'measureRandomFunc' + n;
    window.performance.measure(name, nameStart, nameEnd);
}
 
// 执行三次看看
randomFunc();  
randomFunc();  
// 指定一个名字
randomFunc(888);

// 看下保存起来的标记 mark
window.performance.getEntriesByType('mark')

// 看下保存起来的测量 measure
window.performance.getEntriesByType('measure');

// 清除指定标记
window.performance.clearMarks('markStart888');  
// 清除所有标记
window.performance.clearMarks();
 
// 清除指定测量
window.performance.clearMeasures('measureRandomFunc');  
// 清除所有测量
window.performance.clearMeasures(); 


白屏、首屏渲染时长
性能指标定义衡量指标
白屏用户第一次可以稳定与页面交互的时间FCP
首屏可视区域内容已基本呈现的时间LCP
/*
 * 获取白屏时长 
 * 
 */

function getFirstPaint() {
  let firstPaints = {};
  if (typeof performance.getEntriesByType === 'function'){
    let performanceEntries = performance.getEntriesByType('paint') || [];
    performanceEntries.forEach((entry) => {
      if (entry.name === 'first-paint') {
        firstPaints.firstPaint = entry.startTime;
      } else if (entry.name === 'first-contentful-paint') {
        firstPaints.firstContentfulPaint = entry.startTime;
      }
    });
  } else {
    if (chrome && chrome.loadTimes) {
      let loadTimes = window.chrome.loadTimes();
      let {firstPaintTime, startLoadTime} = loadTimes;
      firstPaints.firstPaint = (firstPaintTime - startLoadTime) * 1000;
    } else if (performance.timing && typeof performance.timing.msFirstPaint === 'number') {
      let {msFirstPaint, navigationStart} = performance.timing; firstPaints.firstPaint = msFirstPaint - navigationStart;
    }
  }
  return firstPaints;
}


/*
 * 获取首屏时长
 */
try {
  const po = new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    const lastEntry = entries[entries.length - 1]; // 优先取 renderTime,如果没有则取 loadTime
    let lcp = lastEntry.renderTime || lastEntry.loadTime;
    window.perfData.push({ 'LCP', lcp });
  });
  po.observe({type: 'largest-contentful-paint'});
} catch (e) { // Do nothing }

FPS 计算(检测页面是否卡顿)
// FPS检测
(() => {
    const limit = 3;                        // 出现低FPS的连续次数上限
    const below = 20;                       // 可容忍的最低FPS
    let count = 0;
    let lastTime = performance.now();
    let frame = 0;
    let lastFameTime = performance.now();
    let fps = 0;
    
    const loop = () => {
        frame += 1;
        const now = performance.now();
        const fs = (now - lastFameTime);
        lastFameTime = now;
        
        // 1000毫秒的 FPS (不需要也是可以的,看你选择)
        fps = Math.round(1000 / fs);
        
        if (now > 1000 + lastTime) {
            // 1s 时间段的FPS
            fps = Math.round((frame * 1000) / (now - lastTime));
            frame = 0;
            lastTime = now;
        }
        if (fps < below) {
            count += 1;
            if (count >= limit) {
                console.log('网页卡顿', `连续${count}次FPS低于${below},当前FPS为${fps}`);
                BUS.trigger('fps-low');    // 关闭一些JS动画
            }
        } else {
            count = 0;
        }
        window.requestAnimationFrame(loop);
    };

    loop();
})();
参考文章
  1. 初探 performance – 监控网页与程序性能
  2. 获取页面加载各个阶段所需时间
  3. Navigation Timing Level 2