跳到主要内容

⚡️ [性能优化] 浏览器中观测 network 性能数据的 3 种方式

· 阅读需 9 分钟
卤代烃

observe-browser-network-hero-image.jpg

前言

📢 招聘

正式开始文章前先插播一条招聘信息。

字节 Web Infra 团队最近开启新一轮招聘了,如果你对 AI/Framework/Rust/Performance 感兴趣,可以来看看我们的岗位(具体岗位信息可以看这个招聘链接 👉 字节跳动 Web Infra - Web Solutions 团队招人啦!

简历可以发到我的邮箱 skychx@hotmail.com,我可以帮你内推岗位并跟进进度 🥳


浏览器的 Devtools 工具有多种观测 network 性能数据的方式,但是这些数据的表现方式各有不同。本文就是解读一下这些性能数据的表现方式,并做纵向比较,帮助更多的人去优化网络带来的性能问题。


常见观测手段

ResourceTiming API

如果想在 runtime 阶段获取当前页面的 network 性能数据,ResourceTiming API 其实是唯一的选择了:

performance.getEntriesByType('resource');

关于这个 API 拿到的性能点位,有一个非常经典的图:

这个图我就不做过多解释了,从我个人角度看,这个 API 是非常优秀的,基本上把网络请求的所有链路都包揽了。这个 API 其实可以大体分为两类:

  • 浏览器相关的点位:Service Worker 和 HTTP Cache
  • 网络相关的点位:DNS/TCP/TLS/HTTP 等

不过这个 API 在跨域场景下部分数据会归 0(ResourceTiming API 数据丢失),这个还是需要注意一下。


Network Panel: Timing

观察网络请求,Network Panel 肯定是最最常用的调试工具了。在性能数据细节这一块儿,主要是通过 Timing 面板来观察数据的:

devtools-network-panel-timing


我先简单的介绍一下 Timing 面板各个字段的含义(其实这部分内容文档都有详细的介绍:Timing Preview

  • Queueing: 网络请求不是有多少发多少,有一个队列的概念。不同的优先级出队时间不一样,HTTP/1.1 还有 6 个连接的限制,这就导致一些比较晚的请求会在队列里排队,这个就是排队所需要的时间
  • Stalled: 请求可能已经建立连接了,但是因为各种原因可能停下来了
  • DNS Lookup: 就 DNS 寻址花费的时间
  • Initial connection: 可以简单理解为 TCP 连接 + TLS 连接耗时;HTTP/3 就是 QUIC 连接耗时
  • SSL: TLS 连接耗时
  • Request sent: 浏览器内部发起请求耗时,一般较短可忽略
  • Waiting (TTFB): HTTP Request 耗时
  • Content Download: HTTP Response 耗时

从上面的内容可以看出,DNS 后面的链路是符合大家对「网络协议」的认知的,一般异议较少。结合 MDN 文档,也能得出下面的相关等式:

// 右侧的点位为 ResourceTiming API 提供
DNS = domainLookupEnd - domainLookupStart
TCP = connectEnd - connectStart
TLS = requestStart - secureConnectionStart
HTTP_Request = responseStart - requestStart
HTTP_Response = responseEnd - responseStart

前面的 QueueingStalled 阶段是和浏览器高度相关的,这里面门道就比较多了。

首先对于普通的开发者来说,你是无法通过 ResourceTiming API 直接得出相关耗时的,你只能通过下面的代码模拟出 Queueing + Stalled:

Queueing + Stalled = Math.min(domainLookupStart, connectStart, secureConnectionStart, requestStart) - startTime

那么 Queueing 和 Stalled 的分界点呢?这个点位 ResourceTiming API 并没有提供,在 Devtools 的源码里,这个点位为 issueTime,既 issueTime 存在且小于 startTime 的情况下会出现 Queueing。

// devtools-frontend/front_end/panels/network/RequestTimingView.ts

// Queueing 场景,issueTime 小于 startTime
if (issueTime < startTime) {
addRange(RequestTimeRangeNames.Queueing, issueTime, startTime);
}

// Blocking 就是 Stalled
const blockingEnd = firstPositive([timing.dnsStart, timing.connectStart, timing.sendStart, responseReceived]) || 0;
addOffsetRange(RequestTimeRangeNames.Blocking, 0, blockingEnd);

这个点位是内核提供的,但是并没有暴露给上层 API,只有 Devtools 消费。所以资源性能调优时,可以把 QueueingStalled 放在一起看。如果这块儿耗时较长,可能的原因和解决方案可能如下:

  • 如果是「HTTP/1.1 六个连接」限制,可以尝试升级到 HTTP/2,或者采用一些通用的资源手段
  • 如果是「资源请求优先级较低」导致的,可以尝试提升请求优先级
  • 如果是「主线程繁忙」导致异步请求被 delay,可以尝试优化主线程 longtask
  • 如果是「浏览器内部资源分配调度耗时」,一般为分配磁盘空间,读磁盘等内部操作,很难去参与优化

至于纯网络请求相关阶段的优化,市面上文章较多,就不展开说了,大家自行查阅即可。


Performance Panel: Network

另一个可以观测 Network 细节的地方,就是 Performance Panel 的 Network Channel,可以看到当前页面的所有网络请求信息。

devtools-perfs-panel-network-channel

图片里的 netwrk waterfall 是一种变异的箱形图,和统计学里的箱型图(box plot)原始概念不一样,这里只是借了形,所以需要重新理解。

首先简单的看一下颜色,这里颜色主要分为 5 类:

  • 蓝色:表示 HTML
  • 紫色:表示 CSS
  • 黄色:表示 JS
  • 绿色:表示 Media 资源,例如 图片/视频/字体
  • 灰色:其它请求,例如 fetch,xhr

我们接下来分析一下形状:

  • 左边的细线:从 startTime 开始,到 requstStart 的所有时间
  • 浅色的箱:既 Request 耗时
  • 深色的箱:既 Response 耗时
  • 右边的细线:资源已经下载好了,但是主线程较忙来不及消费的等待时间

这个用代码表示的化应该是这个:

// 右侧的点位为 ResourceTiming API 提供
Left_Line = requestStart - startTime // Queueing + Stalled + DNS + TCP + TLS
Dark_Bar = responseStart - requestStart // Request
Dark_Bar = responseEnd - responseStart // Response
Right_Line // waiting to be used

从上面可以看出,「左边细线」包含的信息比较多,吞掉了很多细节;「右边细线」目前属于浏览器的运行细节,没有 API 暴露给前端去使用。

具体的优化手段和上面说的差不多,不过值得注意的是右侧的细线,这个一半都是主线程存在 longtask 阻塞了资源的消费,需要把重心放在 longtask 上。


各方法异同

从上面可以看出,观测 network 性能数据的 3 种手段,存在不少共性,但是也各有差异。我做了一张图,基本上一眼就能看出异同:

chrome_devtool_network

注意
  • 本图未考虑 Redirect/Service Worker/HTTP Cache 场景
  • 本图未考虑 HTTP/3 和 TCP Fast Open 场景

总结

在日常调优网络引起的性能时,只有充分理解和掌握相关的调试工具,才能快速准确的定位并解决问题。


参考内容