作者:李振,腾讯云前端性能监控负责人

前言

对于前端来说,最重要的是体验,而在前端体验中,最为核心的就是性能。

相信大多数用户接入前端性能监控(RUM)都是为了通过 RUM 质量评价体系来验证前端性能和质量如何,而直接影响性能和质量的则是一系列的指标,因此了解页面性能指标显得格外重要!

前端性能监控 RUM 是腾讯云的大前端领域页面质量和性能监控平台,聚焦提升用户体验。了解详情

通俗点说,某用户想了解页面访问速度快,是否快,究竟有多快?怎么衡量?需要一个中立的裁判来裁决,而 RUM 的角色正是这个裁判。

本文会结合前端监控 SDK 源码 - Aegis 和 Google 最新的页面性能规范为大家讲解下列两大个主题:

1.前端页面性能关键指标的规范和计算规则。

2.如何看懂 RUM 可视化图表?并通过图表数据进行项目优化?

页面性能指标有哪些?

在前端监控中指标众多繁杂,例如:白屏时间、首屏时间、FCP、FMP、LCP、FID、TTFB等等,一般人难以把握。我们从中抽取一些最常见,最实用的规范跟大家一一解释。

  • 网络连接瀑布图(TL;DR)

要解释这些指标,还是要先祭出网络连接瀑布图,想必只要对页面性能稍有了解的用户都见过这张图。

与这张图一一对应的,是浏览器里面的 performance.timing 属性,我们将其同时打印出来,做一个数据的对比说明。

  • navigationStart: 表示从上一个文档卸载结束时的 unix 时间戳,如果没有上一个文档,这个值将和 fetchStart 相等。
  • unloadEventStart: 表示前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0。
  • unloadEventEnd: 返回前一个页面 unload 时间绑定的回调函数执行完毕的时间戳。
  • redirectStart: 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0。
  • redirectEnd: 最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内部的重定向才算,否则值为 0。
  • fetchStart: 浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前。
  • domainLookupStart/domainLookupEnd: DNS 域名查询开始/结束的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
  • connectStart: HTTP(TCP)开始/重新 建立连接的时间,如果是持久连接,则与 fetchStart 值相等。
  • connectEnd: HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等。
  • secureConnectionStart: HTTPS 连接开始的时间,如果不是安全连接,则值为 0。
  • requestStart: HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存。
  • responseStart: HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存。
  • responseEnd: HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存。
  • domLoading: 开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件。
  • domInteractive: 完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件,注意只是 DOM 树解析完成,这时候并没有开始加载网页内的资源。
  • domContentLoadedEventStart: DOM 解析完成后,网页内资源加载开始的时间,在 DOMContentLoaded 事件抛出前发生。
  • domContentLoadedEventEnd: DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)。
  • domComplete: DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件。
  • loadEventStart: load 事件发送给文档,也即 load 回调函数开始执行的时间。
  • loadEventEnd: load 事件的回调函数执行完毕的时间。

根据上述的定义,我们总结出来常见的页面指标的计算公式:

// 计算加载时间getPerformanceTiming() {const t = performance.timing;const times = {};// 页面加载完成的时间,用户等待页面可用的时间  times.loadPage = t.loadEventEnd - t.navigationStart;// 解析 DOM 树结构的时间  times.domReady = t.domComplete - t.responseEnd;// 重定向的时间  times.redirect = t.redirectEnd - t.redirectStart;// DNS 查询时间  times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;// 读取页面第一个字节的时间  times.ttfb = t.responseStart - t.navigationStart;// 资源请求加载完成的时间  times.request = t.responseEnd - t.requestStart;// 执行 onload 回调函数的时间  times.loadEvent = t.loadEventEnd - t.loadEventStart;// DNS 缓存时间  times.appcache = t.domainLookupStart - t.fetchStart;// 卸载页面的时间  times.unloadEvent = t.unloadEventEnd - t.unloadEventStart;// TCP 建立连接完成握手的时间  times.connect = t.connectEnd - t.connectStart;return times;}

RUM使用了哪些性能指标?

下列我通过各前主流图表来深入了解 RUM 的性能指标。

  • 页面加载瀑布图

瀑布图是表示网站资源如何下载、由引擎解析的图表,其包含首耗时、请求响应等8个性能指标,它让我们可以查看资源之间的顺序和依赖关系。有助于确定加载过程中发生重要事件的位置,还可以让用户轻松看到他们的网站性能的好坏,从而准确显示哪些速度正在减慢网站性能。

Aegis SDK 源码中对其的计算规则如下:

const t: PerformanceTiming = performance.timing;if (!t) return;// 这里不知道为什么有时候 t.loadEventStart - t.domInteractive 返回一个很大的负数,暂时先简单处理let resourceDownload = t.loadEventStart - t.domInteractive;if (resourceDownload < 0) resourceDownload = 1070;result = {dnsLookup: t.domainLookupEnd - t.domainLookupStart,tcp: t.connectEnd - t.connectStart,ssl: t.secureConnectionStart === 0 ? 0 : t.requestStart - t.secureConnectionStart,ttfb: t.responseStart - t.requestStart,contentDownload: t.responseEnd - t.responseStart,domParse: t.domInteractive - t.domLoading,resourceDownload};

备注:resourceDownload 有时会出现一个极大的负数,所以做了简单兼容,取了几个项目的平均值。其他的页面性能指标计算规则大家可以通过代码比较直观的看出来。

RUM  中有一个首屏时间,那么Aegis SDK 是如何计算这个指标的呢?

  1. 默认通过 MutationObserver 这个 API 来监控浏览器 document 对象的 DOM 变化,只计算在首屏内的 DOM 元素,把 DOM 变化时间作为 x 轴,单位时间内 DOM 变化的数量作为 y 轴,绘制曲线后,我们找到 DOM 变化最高点,认为是首屏完成。

  2. 如果开发者觉得该算法不准确,希望自己标记 DOM 元素,可以添加属性 <div AEGIS-FIRST-SCREEN-TIMING>