专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

以用户为中心的性能指标获取

以用户为中心的性能指标

用户体验 描述
是否发生? 导航是否成功启动?服务器是否有响应?
是否有用? 是否已渲染可以与用户互动的足够内容?
是否可用? 用户可以与页面交互,还是页面仍在忙于加载?
是否令人愉悦? 交互是否顺畅而自然,没有滞后和卡顿?

是否发生

first paint / first contentful paint

first paint: 首个元素绘制的时间。
first contentful paint: 首个内容绘制时间,具体指图片或者文本的首个像素渲染。
从定义上这两个指标定义上有所不同,但目前获取的数据却是一致的,我们可以把这两个指标看成我们平时所说的白屏时间。

计算方式

计算方式通过性能对象performance来实现,后面的很多个指标都是利用该对象来获取的,它保存了页面中每一个http请求统计信息。点击这里了解更多关于performance对象。 这个对象在大部分浏览器都支持了,除了IE。

var paintEntries = performance.getEntriesByType('paint');
for (var i = 0; i < paintEntries.length; i++) {
  var entry = paintEntries[i]
  console.log('entry name:', entry.name)
  console.log('spent time', entry.startTime + entry.duration)
}

是否有用

first meaningful paint

第一个有意义的元素渲染时间,有意义指的就是首屏重要元素,所以可以理解为首屏重要元素的渲染,当这个元素渲染出来后,则说明了我们的页面可用了。

计算方式

由于每个页面重要元素不甚相同,所以这个需要用户打点来收集,分别在最开始的位置和重要元素渲染后分别打点。

// 在head的最前面打开始的点
performance.mark('start fmp')
// 在重要元素渲染后打结束的点
performance.mark('end fmp')
// 计算first meaningful paint时间
var entry = performance.measure('fmp', 'start fmp', 'end fmp')
console.log('spent time', entry.startTime + entry.duration)

关于开始打点的位置,我们可以在head的最前端添加script代码,作为开始的点。
关于重要元素渲染后的打点,如果我们用react/vue,我们可以把这个重要元素提取为一个组件,然后在componentDidMount中打点。如果是vue,则在mounted方法打点。如果是普通html,则可以在重要到元素下面添加script,然后script里面打点。
如果图片是重要元素,则可以计算首图时间来作为fmp,后面会讲到。

首图加载时间

首图时间是页面中首张图片的加载时间,计算首屏时间的时候,如果首屏中有图片,则首屏时间就是首图时间。

计算方式

计算首图时间,我们同样可以利用performance来获取图片加载详情。我们需要标记哪些图片是首图,这里我们给首图元素(不一定是img元素)加个perf-img=”true”来标示首图元素,可以多个。

// html
<img src={logo} width={224} perf-img="true"/>

// js

// 获取首图元素的图片地址,首图元素可能是img/元素的background
function getImgSrc(dom) {
    var imgSrc;
    if (dom.nodeName.toUpperCase() == 'IMG') {
      imgSrc = dom.src;
    } else {
      var computedStyle = window.getComputedStyle(dom);
      var bgImg = computedStyle.getPropertyValue('background-image') ||         computedStyle.getPropertyValue('background');
      var matches = bgImg.match(/url\(.*?\)/g);
      if (matches && matches.length) {
        var urlStr = matches[matches.length - 1]; // use the last one
        var innerUrl = urlStr.replace(/^url\([\'\"]?/, '').replace(/[\'\"]?\)$/, '');
        if (((/^http/.test(innerUrl) || /^\/\//.test(innerUrl)))) {
          imgSrc = innerUrl;
        }
      }
    }
    return imgSrc;
}
var entries = performance.getEntriesByType('resource');
for (var i = 0; i < paintEntries.length; i++) {
    var entry = entries[i];
    if (entry.initiatorType === 'img') {
        var $mainImgs = document.querySelectorAll('[perf-img]');
        var len = $mainImgs.length;
        for (var i = 0; i < len; i++) {
            var $mainImg = $mainImgs[i];
            // 如果加载的是首图图片
            if (entry.name === getImgSrc($mainImg)) {
              console.log('spent time', entry.startTime + entry.duration)
            }
        }
    }
}

是否令人愉悦

long task

js是单线程的,所有的任务都需要放在主线程的队列中执行,浏览器的ui任务和js任务都需要放在队列中等待主线程空闲后执行,如果js任务耗时较长,则会导致ui无法渲染,用户无法和页面交互,用户就会感知到页面滞后或者卡顿。获取long task需要利用PerformanceObserver对象,这个对象可以监控long task。

计算方式

  var longTaskObserver = new PerformanceObserver((list) => {
    var entries = list.getEntries();
    for (var i = 0, len = entries.length; i < len; i++) {
      var entry = entries[i];
      var time = entry.startTime + entry.duration;
      console.log('long task spent', time);
    }
  });

  longTaskObserver.observe({ entryTypes: ['longtask'] });

是否可用

time to interactive

简称tti,可交互时间,表示用户是否可以通过点击、输入等和页面交互。用户无法和页面进行交互到原因有大概有这几个: 1、渲染页面/组件的js未加载
2、js任务耗时长
3、加载页面/组件需要的接口阻塞

计算方式

谷歌提供了ttiPolyfill的sdk来计算(实际上它也是计算long task),但它不适用于第三点, 而貌似第三点很普遍存在,拉接口-渲染数据。 所以我们需要有个可靠的方法来计算。如果我们仔细想想,计算tti可以用和计算fmp一样的方法,我们把有意义的元素换成可交互的元素,然后在该元素渲染后打点,虽然需要手动打点,但比较可靠。

其他

首api(重要api)加载时间 / 首js(重要js)加载时间 / 首css(重要css)加载时间

这几个指标同样重要: 如果重要api未加载下来,页面可能无法显示,则页面“不可用”-是否可用。
如果重要js未加载下来,页面就是白屏,则页面“没发生”-是否发生。
如果重要css未加载下来,页面没有样式,则页面“不可用”为否-是否可用。

计算方式

这几个指标的计算方式同首图的计算方式一样,通过performance获取resource类型的entry即可。

var __performance = {
    firstApi: 0,
    firstCss: 0,
    firstJs: 0,
    firstImg: 0
}

var entries = performance.getEntries('resource');
for (var i = 0, len = entries.length; i < len; i++) {
  var entry = entries[i];
  const time = entry.startTime + entry.duration;
  switch (entry.initiatorType) {
    case 'xmlhttprequest':
      if ("/api/v1/users/profile" === entry.name || "/api/v1/users/status")) {
        // 直接覆盖上个的值,至于为什么,下面会提到
        __performance.firstApi = time;
      }
      break;
    case 'img':
      var $mainImgs = document.querySelectorAll('[perf-img]');
      var len = $mainImgs.length;
      for (var i = 0; i < len; i++) {
        var $mainImg = $mainImgs[i];
        if (entry.name === getImgSrc($mainImg)) {
            __performance.firstImg = time;
        }
      }
      break;
    case 'link':
      if (https://tech.souyunku.com/app.*\.css/.test(entry.name)) {
        __performance.firstCss = time;
      }
      break;
    case 'script':
      if (https://tech.souyunku.com/app.*\.js/.test(entry.name)) {
        __performance.firsJs = time;
      }
      break;
  }
}

同个指标多个值问题

对于首图、首api、首js、首css,都可能包含多个,我们以首api为例。
如果first api有多个,那么以哪个为准还是两个的值叠加呢?这分两种情况讨论:
1、多个并行请求,我们以耗时最长的请求为准。 2、多个请求串行,则应该多个请求的时间叠加合。
对于第一种情况,我们获取数据的方法很简单,以请求耗时最长的为准:

对于第二种情况,我们需要叠加两个请求的startTime+duraton吗?实际上不需要,PerformanceObserver帮我们做了这个工作,第二个请求的startTime=第一个请求耗时,所以我们获取指标的时候还是这样:

var time = entry.startTime + entry.duration

最后以time最大的值为准。

何时获取&上报数据

最理想的情况就是当页面稳定的时候获取然后上报数据,这样可以获取到全面的数据。但是页面稳定的时候是个无解的问题,我们只能在页面onload后取个合适的时间,这个时间建议5s,如果5s后页面还没稳定,用户就会觉得卡顿,这和用户感知相关。
用户感知长度表:

用户感知 响应时间
流畅 < 1s
可用 1s ~ 2s
丢帧 纯页面性能指标
卡顿 3s ~ 5s
阻塞 > 5s
function report() {
    setTimeout(function() {
        // 利用上面的代码获取到各个指标后这里获取
        console.log(window.__performance)
    }, 5000)
}
if (document.readyState == 'complete') {
    report();
} else {
    window.addEventListener('load', () => {
      report();
    });
}

对于如何判断页面5s后某些指标仍然未获取得到,则判定为页面有阻塞:

1、 fp/fcp 5s后为0(一直白屏)
2、 首配置了首js,5s后仍然为0(说明5s后js未加载下来)
3、 如果配置了首api,5s后仍然为0(说明5s后接口还未拉取回来)
4、 如果手动打点fmp,5s后仍然为0或者fmp耗时超过5s(重要元素渲染超过5s)
5、 如果手动打点了tti,tti耗时超过5s(页面到可响应时间超过5s)
6、 long task超过5s(js阻塞超过5s)

如果判定页面阻塞,则5s后我们获取到的数据就是超时。

最后附上源码:
源码

参考链接

文章永久链接:https://tech.souyunku.com/42915

未经允许不得转载:搜云库技术团队 » 以用户为中心的性能指标获取

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们