如何采集和分析网页用户的性能指标

背景:

性能优化很重要,可是咱们怎么知道本身的网页性能如何呢?如何测算?很多人会说,我打开很快呀,不到1秒,可是别人呢?css

要正确地分析咱们网页应用的性能指标,这种方法比较靠谱的呢!html

横轴表明加载时间,纵轴表明用户数(其实还能够加上地区等指标的筛选,It's up to you!).只有这样咱们才能避免坐井观天,看到咱们的应用在全部用户中的加载状况。react

性能指标

咱们知道,用GA能够采集不少信息,包括页面停留时间,DOM加载时间,以及页面加载时间等等,可是,评判一个网页的性能,是有优先级之分的。git

Any performance metric that values all the content the same is not a good metric.github

有哪些指标是咱们须要关注的呢?当用户导航到网页时,一般会寻找视觉反馈,以确信一切符合预期。web

体验 指标 具体状况
是否发生? 首次绘制 (FP)/首次内容绘制 (FCP) 导航是否成功启动?服务器是否有响应?
是否有用? 首次有效绘制 (FMP)/主角元素计时 是否已渲染能够与用户互动的足够内容?
是否可用? 可交互时间 (TTI) 用户能够与页面交互,仍是页面仍在忙于加载?
是否使人愉快? 耗时较长的任务(Long Task) 交互是否顺畅而天然,没有滞后和卡顿?

下列加载时间线屏幕截图有助于您更直观地了解加载指标对应的加载体验:(lighthouse检查结果截图)
性能优化


从左到右能够看到,直到第三张图页面才不是空白,因此

  1. FP => 第三张图
  2. FCP => 第四张图
  3. FMP => 第五张图(假设转盘为主角,转盘代码开源)
  4. TTI => 也是第五张图(不过不必定的呢,这个可能等全部sync js加载完才知道)

咱们能够用LighthouseWeb Page Test 等这些工具用于在功能发布前测试其性能,但这些工具并未在用户设备上运行,所以未反映出用户的实际性能体验。bash

这里咱们用几个新增的API,他们是PerformanceObserverPerformanceEntryDOMHighResTimeStamp 以及 performance服务器

如下是PerformanceEntry的可选项:async

跟踪FP/FCP

const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // `name` will be either 'first-paint' or 'first-contentful-paint'.
      const metricName = entry.name;
      const time = Math.round(entry.startTime + entry.duration);
      //发送数据到后台
      request.post({
        eventCategory:'Performance Metrics',
        eventAction: metricName,
        eventValue: time,
        nonInteraction: true,
      })
    }
  });
  observer.observe({entryTypes: ['paint']});
复制代码

能够看到用PerformanceObserver观察entryTypes为paint,也就是FP/FCP的timing.每个entry是一个PerformanceEntry对象。

跟踪FMP

肯定页面上的主角元素以后,您能够跟踪为用户呈现这些元素的时间点。
记住!不管用什么方法,你应该尽快让主角元素呈现出来

<img src="hero.jpg" onload="performance.clearMarks('img displayed'); performance.mark('img displayed');">
<script>
performance.clearMarks("img displayed");
performance.mark("img displayed");
</script>
复制代码

假如咱们的主角元素是一张图片,这时候咱们在图片加载出来前先mark一下,而后在onload时间在mark一下,二者再相减,就能够知道咱们主角元素出来的用时了

跟踪TTI

这里咱们使用tti-polyfill

import ttiPolyfill from 'tti-polyfill';

ttiPolyfill.getFirstConsistentlyInteractive().then((tti) => {
 request.post({
   eventCategory:'Performance Metrics',
   eventAction:'TTI',
   eventValue: tti,
   nonInteraction: true,
 });
});
复制代码

getFirstConsistentlyInteractive() 方法接受可选的 startTime 配置选项,让您能够指定下限值(您知道您的应用在此以前没法进行交互)。 默认状况下,该 polyfill 使用 DOMContentLoaded 做为开始时间,但一般状况下,使用主角元素呈现的时刻或您知道全部事件侦听器都已添加的时间点这类时间会更准确。

image.png

跟踪Long tasks

Long time在 style layout, paint,script,都会出现。

image.png

image.png

image.png

咱们能够在Dev Tools找到存在的long-task。

要在 JavaScript 中检测耗时较长的任务,须要建立新的 PerformanceObserver,并观察类型为 longtask 的条目。 耗时较长的任务条目的一个优势是包含提供方属性,有助于您更轻松地追查致使出现耗时较长任务的代码:

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    request.post({
      eventCategory:'Performance Metrics',
      eventAction: 'longtask',
      eventValue:Math.round(entry.startTime + entry.duration),
      eventLabel:JSON.stringify(entry.attribution),
    });
  }
});

observer.observe({entryTypes: ['longtask']});
复制代码

跟踪输入延迟

若要在代码中检测输入延迟,您可将事件时间戳与当前时间做比较,若是二者相差超过 100 毫秒,您能够并应该进行报告。

const testBtn = document.querySelector('#testBtn');

testBtn.addEventListener('click', (event) => {
  // Event listener logic goes here...

  const lag = performance.now() - event.timeStamp;
  if (lag > 100) {
    request.post({
      eventCategory:'Performance Metric'
      eventAction: 'input-latency',
      eventLabel: '#subscribe:click',
      eventValue:Math.round(lag),
      nonInteraction: true,
    });
  }
});
复制代码

记录丢失的用户(统计用户在页面的时长)

有些时候,用户会由于页面加载时间过长而选择离开,可是他到底忍受了多久呢?
咱们能够用visibilitychange(该事件在页面卸载或进入后台时触发)记录

<script>
window.trackLeaver = () => {
  // Remove the listener so it only runs once.
  document.removeEventListener('visibilitychange', window.trackLeaver);

  // Send the data to Google Analytics via the Measurement Protocol.
  navigator.sendBeacon && navigator.sendBeacon(ANALYTICS_URL, [
    'v=1', 't=event', 'ec=Load', 'ea=abandon', 'ni=1',
    'dl=' + encodeURIComponent(location.href),
    'dt=' + encodeURIComponent(document.title),
    'tid=' + TRACKING_ID,
    'cid=' + CLIENT_ID,
    'ev=' + Math.round(performance.now()),
  ].join('&'));
};
document.addEventListener('visibilitychange', window.trackLeaver);
</script>
复制代码

其余指标

1.采集css/js的加载时间

直接在全部css文件后加一下performace.mark就能够了, 不过内联的css或者async js都不行哦。

2.采集字体的加载时间

使用 fontfaceobserver,

performance.clearMarks("img displayed");
performance.mark("img displayed");
new w.FontFaceObserver( "lato" )
	.check()
	.then( function(){ console.log( “Loaded!” );
	performance.clearMarks("img displayed");
performance.mark("img displayed");});
复制代码

方法相似于采集主角元素的加载时间。

3.采集用户Dom渲染的时间

参考 React16.9 Profiler,

function onRenderCallback(
  id, // the "id" prop of the Profiler tree that has just committed
  phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
  actualDuration, // time spent rendering the committed update
  baseDuration, // estimated time to render the entire subtree without memoization
  startTime, // when React began rendering this update
  commitTime, // when React committed this update
  interactions // the Set of interactions belonging to this update
) {
  // Aggregate or log render timings...
}
复制代码

在RenderCallback里面上报就能够了。

总结

咱们作优化必定是为了有效果,若是咱们作出来最后的效果还不如之前就坏了,因此咱们要本身先测试,而后先灰度观察,再全量。

这里方法只是为了有机会帮助咱们制定优化策略,要取决于咱们的人力,时间,技术等成本。

参考文章

  1. developers.google.com/web/fundame…
  2. speedcurve.com/blog/user-t…
  3. w3c.github.io/performance…
  4. www.filamentgroup.com/lab/font-ev…
  5. w3c.github.io/longtasks/
  6. www.stevesouders.com/blog/2015/0…
  7. github.com/GoogleChrom…
  8. docs.google.com/document/d/…
相关文章
相关标签/搜索