Contents

性能监控基础

性能监控基础

前端应用渲染模型

  • CSR:Client Side Rendering,客户端(通常是浏览器)渲染;
  • SSR:Server Side Rendering,服务端渲染;
  • SSG:Static Site Generation,静态网站生成;
  • ISR:Incremental Site Rendering,增量式的网站渲染;
  • DPR:Distributed Persistent Rendering,分布式的持续渲染。

详解:https://cloud.tencent.com/developer/article/1819396

指标

性能标准

  • Navigation Timing:提供了文档导航过程中完整的计时信息,即一个文档从发起请求到加载完毕各阶段的性能耗时
  • Performance Timeline:提供了获取各种类型(navigation、resource、paint 等)的性能时间线的方法
  • Resource Timing:提供文档中资源的计时信息
  • Paint Timing:记录在页面加载期间的一些关键时间点
  • Long Tasks API:检测长任务的存在,长任务会在很长一段时间内独占UI线程,并阻止其他关键任务的执行 —— 例如响应用户输入
  • User Timing:定义了一些接口来帮助 web 开发人员通过访问高精度的时间戳来衡量他们的应用程序的性能。

传统性能指标

传统的性能指标主要依赖 Navigation Timing 以及 Navigation Timing 2,通过记录一个文档从发起请求到加载完毕的各阶段的性能耗时,以加载速度来衡量性能

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/68a7aaee31f7406984054807d0b43c78~tplv-k3u1fbpfcp-zoom-in-crop-mark%3A4536%3A0%3A0%3A0.image

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/image-20221116104547242.png

  • 初始化阶段
    • navigationStart**(Level 1)**:用户完成卸载前一个文档的时间点
    • redirectStart:页面重定向时的开始时间,或者是 0
    • redirectEnd:页面重定向的结束时间,或者是 0
  • 请求阶段
    • workStart**(Level 2)**:Service Worker 初始化的时间
    • fetchStart:浏览器发起资源请求。有缓存时,则返回读取缓存的开始时间
    • domainLookupStart:查询 DNS 的开始时间
    • domainLookupEnd:查询 DNS 的结束时间
    • connectStart:浏览器开始与服务器连接时的时间
    • secureConnectionStart:如果页面使用 HTTPS,它的值是安全连接握手之前的时刻
    • connectEnd:当浏览器端完成与服务器端建立连接的时刻
    • responseStart:指客户端收到从服务器端(或缓存、本地资源)响应回的第一个字节的数据的时刻
    • responseEnd:指客户端收到从服务器端(或缓存、本地资源)响应回的最后一个字节的数据的时刻
  • 解析渲染阶段
    • domLoading**(Level 1)**:浏览器即将开始解析第一批收到的 HTML 文档字节,Document.readyState 变为 loading
    • domInteractive:当前网页 DOM 结构结束解析、开始加载内嵌资源的时间点,Document.readyState 变为 interactive
    • domContentLoadedEventStart:当解析器发送 DomContentLoaded 事件,所有需要被执行的脚本已经被解析
    • domContentLoadedEventEnd:所有需要立即执行的脚本已经被执行
    • domComplete:当前文档解析完成, Document.readyState 变为 complete
    • loadEventStart:作为每个网页加载的最后一步,浏览器会触发 load 事件,以便触发额外的应用逻辑。如果这个事件还未被发送,它的值将会是 0
    • loadEventEnd:load 事件执行完成。如果这个事件还未被发送,或者尚未完成,它的值将会是 0

计算关键阶段的耗时:

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/image2022-7-1_14-3-46.png

各阶段耗时
  • Redirect:redirectEnd - redirectStart // 重定向
  • App cache:domainLookupStart - fetchStart // 缓存
  • DNS:domainLookupEnd - domainLookupStart // DNS解析
  • TCP:connectEnd - connectStart // TCP建连
  • SSL:connectEnd - secureConnectionStart // SSL握手
  • Request:responseStart - requestStart // 请求耗时
  • Response:responseEnd - responseStart // 响应耗时
  • Processing:domComplete - domLoading // 文档接收与解析
实际运用中的常见指标
  • Load:loadEventEnd - navigationStart // 页面完全加载耗时
  • DOMReady:domContentLoaded - fetchStart // dom ready 耗时
  • DOMParse:domInteractive - responseEnd // dom 解析耗时
  • TTFB:responseStart - navigationStart // Time To First Byte 是发出页面请求到接收到应答数据第一个字节的时间总和,它包含了DNS解析时间、 TCP连接时间、发送HTTP请求时间和获得响应消息一个字节的时间
  • ResourceLoad: domComplete - domContentLoaded // 剩余资源加载耗时

数据采集

Navigation Timing Level 1 可以通过 window.performance.timing 采集。浏览器兼容性 可见这里

{
    "connectStart": 1668506552731,
    "navigationStart": 1668506552724,
    "secureConnectionStart": 0,
    "fetchStart": 1668506552731,
    "domContentLoadedEventStart": 1668506556535,
    "responseStart": 1668506553334,
    "domInteractive": 1668506556535,
    "domainLookupEnd": 1668506552731,
    "responseEnd": 1668506553350,
    "redirectStart": 0,
    "requestStart": 1668506552756,
    "unloadEventEnd": 1668506553368,
    "unloadEventStart": 1668506553367,
    "domLoading": 1668506553465,
    "domComplete": 1668506566907,
    "domainLookupStart": 1668506552731,
    "loadEventStart": 1668506566907,
    "domContentLoadedEventEnd": 1668506556536,
    "loadEventEnd": 1668506566907,
    "redirectEnd": 0,
    "connectEnd": 1668506552731
}

Navigation Timing Level 2 可以通过 window.performance.getEntriesByType(’navigation’) 采集,浏览器兼容性可见

[
    {
        "name": "http://localhost:7000/pub/cspace/budget/list-detail?obsId=3FO4K4WCG3M3",
        "entryType": "navigation",
        "startTime": 0,
        "duration": 14182.399999976158,
        "initiatorType": "navigation",
        "nextHopProtocol": "http/1.1",
        "renderBlockingStatus": "blocking",
        "workerStart": 0,
        "redirectStart": 0,
        "redirectEnd": 0,
        "fetchStart": 6.5,
        "domainLookupStart": 6.5,
        "domainLookupEnd": 6.5,
        "connectStart": 6.5,
        "connectEnd": 6.5,
        "secureConnectionStart": 0,
        "requestStart": 31.69999998807907,
        "responseStart": 609.6999999880791,
        "responseEnd": 625.8000000119209,
        "transferSize": 12010,
        "encodedBodySize": 11710,
        "decodedBodySize": 11710,
        "serverTiming": [],
        "unloadEventStart": 642.1999999880791,
        "unloadEventEnd": 643.8999999761581,
        "domInteractive": 3811.100000023842,
        "domContentLoadedEventStart": 3811.100000023842,
        "domContentLoadedEventEnd": 3811.899999976158,
        "domComplete": 14182.199999988079,
        "loadEventStart": 14182.399999976158,
        "loadEventEnd": 14182.399999976158,
        "type": "reload",
        "redirectCount": 0
    }
]

缺点

在 Navigation Timing 记录了很多在网页导航和加载过程中的关键过程以及每个过程发生的时间点。通过计算也可以得到关键过程的耗时,但是这些耗时指标只能衡量在页面完全加载前的情况,这个过程未必就是用户所关系的。例如,服务器可以用一个可以立即 “加载” 的最小页面来进行响应,然后延迟获取内容并在页面上延迟显示任何内容,直到 load 事件触发的几秒钟后。虽然这样的页面或许确实具备较快的加载时间,但这与用户实际体验页面加载的方式不符。

为了能够切实的度量用户所关心网页性能指标,在过去的几年中,W3C Web 性能工作组 一直在致力于打造一组新的标准化 API 和指标。

以用户为中心的性能指标

以上说到的那些性能指标都是较传统的性能指标,它们专注于容易衡量的技术细节,并不一定与用户在屏幕上看到的内容相对应,但 很难反应出用户所真正关心的是什么。如果仅仅是关注把页面加载速度优化得更快,那么就很快会发现网站的用户体验依然很差。这就是创建用户为中心的性能指标的原因,它们专注于 用户视角下 的浏览体验。

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/image-20221116111250361.png

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/image-20221116111327352.png

上面展示了两张不同页面加载模式图,在相同加载耗时的情况下,右边通过渐进式加载方式加载的页面显然会让用户感到更舒适,因为在很快的时间内就看到有内容渲染到视口,然后慢慢的视口的内容绘制的越来越多,而不是让用户在焦急的等待很长时间后,一下子将页面的几乎所有内容比较突兀进行呈现。

据分析,用户对页面感到是否 “快” 的看法,会受到加载体验中不同时刻的影响(用户是否可以与之进行交互,以及这些交互是否流畅且无延迟)。以下指标试图从四个不同的角度捕捉这一点:

用户体验 描述 指标
是否正在发生? 导航是否成功启动?服务器有响应吗? FP(First Paint )FCP(First Contentful Paint )TTFB(Time To First Byte)
是否有用? 是否渲染了足够的内容让用户可以深入其中? LCP(Largest Contentful Paint)FMP(First Meaningful Paint )SI(Speed Index)
是否可用? 页面是否繁忙,用户是否可以与页面进行交互? TTI(Time to Interactive )TBT(Total Blocking Time)
是否令人愉悦? 交互是否流畅自然,没有延迟和卡顿? FID(First Input Delay )CLS(Cumulative Layout Shift)

详细说明

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/image-20221116112025295.png

是否正在发生?—— FP & FCP & TTFB

First Paint(FP)

首次渲染时间点,测量第一个像素出现在视窗中所花费的时间,从而呈现与之前显示的相比的任何视觉变化。这可以是来自文档对象模型(DOM)的任何形式,例如背景颜色、画布或图像。FP 可以帮助开发人员了解在呈现页面时是否发生了意外情况。在 FP 时间点之前,用户看到的都是没有任何内容的白色屏幕。

First Contentful Paint(FCP)

首次内容绘制时间点,测量视口中第一个内容渲染的时间。这可以是来自文档对象模型(DOM)的任何形式,例如图像、 SVG 或文本块。FCP 经常与第一涂料(FP)重叠。FCP 帮助开发人员了解用户看到页面上的任何内容变化需要多长时间。在用户访问 Web 网页的过程中,FCP 时间点之前,用户看到的都是没有任何实际内容的屏幕。FCP 反映当前 Web 页面的网络加载性能情况、页面 DOM 结构复杂度情况、inline script 的执行效率的情况。当所有的阶段性能做的非常好的情况下,首次出现内容的时间就会越短,用户等待的时间就会越短,流失的概率就会降低。

Time To First Byte(TTFB)

首字节达到时间点,测量用户的浏览器接收页面内容的第一个字节所需的时间。TTFB 帮助开发人员了解他们的速度慢是由初始响应引起的,还是由呈现阻塞内容引起的

否有用?—— LCP & FMP & SI

Largest Contentful Paint(LCP)

最大内容渲染时间点,,也就是最大的内容在可视区域内变得可见的时间点,测量显示在视窗中的最大内容的渲染时间。这可以是来自文档对象模型(DOM)的任何形式,例如图像、 SVG 或文本块。它是视窗中最大的像素区域,因此最具视觉效果。LCP 帮助开发人员了解用户需要多长时间才能看到页面上的主要内容

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/image-20221116112221233.png

FMP(First Meaningful Paint)

首次绘制有意义内容的时间点,当整体页面的布局和文字内容全部渲染完成后,可认为是完成了首次有意义内容的绘制。FMP 通常被认为是用户获取到了页面主要信息的时刻,也就是说此时用户的需求是得到了满足的,所以产品通常也会关注 FMP 指标

FMP代码实现原理:

理论依据:认为「DOM 结构变化的时间点」与之对应「渲染的时间点」近似相同。所以 FMP 的时间点为 「DOM 结构变化最剧烈的时间点」。「DOM 结构变化的时间点」可以利用 MutationObserver API 来获得。

  1. 通过 MutationObserver 监听每一次页面整体的 DOM 变化,触发 MutationObserver 的回调
  2. 在回调计算出当前 DOM 树的分数,分数变化最剧烈的时刻,即为 FMP 的时间点
SI(Speed Index)

衡量页面可视区域加载速度,帮助检测页面的加载体验差异。下图展示了 A、B 两个页面在首次内容出现和完全加载时间是一样的情况下,内容不同的加载方式。可以很明显的发现,从用户角度看 A 页面的体验明显是更好的

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/image-20221116112213305.png

是否可用?—— TTI & TBT

Time to Interactive(TTI)

可交互时间,测量页面从开始加载到主要子资源完成渲染,并能够快速、可靠地响应用户输入所需的时间。是安静窗口之前最后一个长任务的结束时间(如果没有找到长任务,则与 FCP 值相同)。TTI 反映页面可用性的重要指标。TTI 值越小,代表用户可以更早地操作页面,用户体验就更好

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/image-20221116112525705.png

TTI 算法实现:

  • 首先进行首次内容绘制(FCP
  • 沿时间轴正向搜索时长至少为 5 秒的安静窗口,其中,安静窗口的定义为:没有长任务且不超过两个正在处理的 GET 网络请求
  • 沿时间轴反向搜索安静窗口之前的最后一个长任务,如果没有找到长任务,则在 FCP 步骤停止执行
  • TTI 是安静窗口之前最后一个长任务的结束时间(如果没有找到长任务,则与 FCP 值相同)
Total Blocking Time(TBT)

总阻塞时间,测量 First Contentful Paint 首次内容绘制 (FCP)Time to Interactive 可交互时间 (TTI) 之间的总时间。在这期间,主线程被阻塞的时间过长,无法作出输入响应。量化主线程在空闲之前的繁忙程度,有助于理解在加载期间,页面无法响应用户输入的时间有多久。

长任务:如果一个任务在主线程上运行超过 50 毫秒,那么它就是长任务。超过 50ms 部分的任务耗时,都算作任务的阻塞时间。

每当出现长任务(在主线程上运行超过 50 毫秒的任务)时,主线程都被视作 “阻塞状态”。说主线程处于 “阻塞状态” 是因为浏览器无法中断正在进行的任务。因此,如果用户在某个长任务运行期间与页面进行交互,那么浏览器必须等到任务完成后才能作出响应。如果任务时长足够长(例如超过 50 毫秒),那么用户很可能会注意到延迟,并认为页面缓慢或卡顿。某个给定长任务的阻塞时间是该任务持续时间超过 50 毫秒的部分。一个页面的总阻塞时间是在 FCP 和 TTI 之间发生的每个长任务的阻塞时间总和。

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/image-20221116112924384.png

TTI 有时可能会误导用户,但当与 TBT 结合使用时,就会更清楚地了解页面对用户输入的响应程度。

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/image-20221116112932335.png

是否令人愉悦?—— FID & CLS

FID(First Input Delay)

首次输入延迟,测量从用户第一次与页面交互(比如当他们单击链接、点按按钮等等)直到浏览器对交互作出响应,实际能够开始处理事件 处理程序所经过的时间。操作可能包括单击按钮、链接或其他自定义 Javascript 控制器。FID 在应用程序页面上提供关于成功或不成功交互的关键数据

  • FID 源自 Event timing 标准,深入了解由用户交互触发的事件的延迟。通常情况下,Input Delay 是因为浏览器主线程在忙于执行其他操作,无暇处理用户的交互操作
  • FID 反映用户对页面交互性和响应性的第一印象,良好的第一印象有助于用户建立对整个应用的良好印象
  • 页面加载阶段,资源的处理任务最重,也最容易产生输入延迟。因此关注 FID 指标对于提升页面的可交互性有很大收益
  • FID 和页面加载完成后的 Input Delay 具有不同的解决方案
    • 针对 FID,我们一般建议通过 Code Splitting 等方式减少页面加载阶段 JS 的加载、解析和执行时间
    • 针对页面加载完成后的 Input Delay,通常是由于开发人员代码编写不当、引起 JS 执行时间过长而产生的
CLS(Cumulative Layout Shift)

累积布局移位,是渲染过程中每个意外元素移位的单个布局移位得分的总和,量化了在页面加载期间,视口中元素的移动程度。想象一下,导航到一篇文章,并试图在页面加载完成之前单击一个链接。在光标到达之前,链接可能已经由于图像渲染而向下移动。CLS 评分代表了破坏性和视觉不稳定性转移的程度,而不是使用持续时间来表示这个 Web Vital

https://raw.githubusercontent.com/lxw15337674/PicGo_image/main/7ju5i-y2lnc.gif

上面的 GIF 展示了:当我们点击按钮时,突然出现了一块内容。无论是以一种增加意外点击几率的方式加载广告,还是在加载图片时文本向下移动,内容的意外移动都会让人非常不舒服。

CLS 通过测量偏移度,来帮助你解决这个问题。它引入了用户体验中一个全新的类别 —— 可预测性。CLS 分数越低越好,因为这意味着在整个页面交互过程中发生的内容的偏移较少。

每个布局移位得分是使用冲击和距离分数计算的。影响分数是元素影响两个渲染帧之间的总可见区域。距离分数测量它相对于视窗移动的距离。

布局偏移分数 = 影响分数 * 距离分数

让我们看看上面的例子,它有一个不稳定的元素——正文文本。影响分数大约是页面的 50% ,并将正文文本向下移动 20% 。布局移位得分为 0.1,乘以 0.5 * 0.2。因此,CLS 是 0.1。关于 CLS 的计算细节和原理可以查看 web.dev讲述累积布局偏移 (CLS)

相关文档

https://web.dev/user-centric-performance-metrics/

https://calibreapp.com/blog/new-generation-of-performance-metrics

https://web.dev/navigation-and-resource-timing/

https://web.dev/i18n/zh/rail/

https://web.dev/defining-core-web-vitals-thresholds/

https://chromium.googlesource.com/chromium/src/+/master/docs/speed/metrics_changelog/README.md

https://web.dev/performance-scoring/

https://blog.chromium.org/2020/05/the-science-behind-web-vitals.html