如何使用导航时间和资源时间评估现场加载性能

了解使用导航和资源时间 API 评估现场加载性能的基础知识。

发布时间:2021 年 10 月 8 日

如果您在浏览器的开发者工具(或 Chrome 中的 Lighthouse)中使用过网络面板中的连接节流来评估加载性能,那么您就会知道这些工具对于性能调优有多么方便。您可以使用一致且稳定的基准连接速度快速衡量性能优化带来的影响。唯一的问题是,这是综合测试,产生的是 实验室数据,而不是 现场数据

综合测试本身并非不好,但它不能代表您的网站对真实用户的加载速度。这需要现场数据,您可以从导航时间和资源时间 API 收集这些数据。

帮助您评估现场加载性能的 API

导航时间和资源时间是两个类似的 API,它们之间有很大的重叠,但衡量的是两个不同的方面

  • 导航时间衡量 HTML 文档请求(即导航请求)的速度。
  • 资源时间衡量文档依赖资源(如 CSS、JavaScript、图像和其他资源类型)请求的速度。

这些 API 在性能条目缓冲区中公开其数据,可以使用 JavaScript 在浏览器中访问该缓冲区。有多种方法可以查询性能缓冲区,但常用的方法是使用 performance.getEntriesByType

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

performance.getEntriesByType 接受一个字符串,描述您要从性能条目缓冲区检索的条目类型。'navigation''resource' 分别检索导航时间和资源时间 API 的计时。

这些 API 提供的信息量可能很大,但它们是您衡量现场加载性能的关键,因为您可以从访问您网站的用户那里收集这些计时。

网络请求的生命周期和计时

收集和分析导航和资源计时有点像考古学,因为您是在事后重建网络请求短暂的生命周期。有时,可视化概念会有所帮助,就网络请求而言,浏览器的开发者工具可以提供帮助。

Network timings as shown in Chrome's DevTools. The timings depicted are for request queueing, connection negotiation, the request itself, and the response in color-coded bars.
Chrome DevTools 网络面板中网络请求的可视化

网络请求的生命周期有不同的阶段,例如 DNS 查询、连接建立、TLS 协商以及其他延迟来源。这些计时表示为 DOMHighResTimestamp。根据您的浏览器,计时的粒度可能精确到微秒,或者向上舍入到毫秒。您需要详细检查这些阶段,以及它们与导航时间和资源时间的关系。

DNS 查询

当用户访问 URL 时,会查询域名系统 (DNS) 以将域名转换为 IP 地址。此过程可能需要相当长的时间,即使在现场也需要测量此时间。导航时间和资源时间公开了两个与 DNS 相关的计时

  • domainLookupStart 是 DNS 查询开始的时间。
  • domainLookupEnd 是 DNS 查询结束的时间。

可以通过从结束指标中减去开始指标来计算 DNS 查询的总时间

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

连接协商

影响加载性能的另一个因素是连接协商,这是连接到 Web 服务器时产生的延迟。如果涉及 HTTPS,此过程还将包括 TLS 协商时间。连接阶段由三个计时组成

  • connectStart 是浏览器开始打开与 Web 服务器的连接的时间。
  • secureConnectionStart 标记客户端开始 TLS 协商的时间。
  • connectEnd 是与 Web 服务器的连接已建立的时间。

测量总连接时间类似于测量 DNS 查询的总时间:您从结束计时中减去开始计时。但是,如果未使用 HTTPS 或 连接是持久连接,则可能有一个额外的 secureConnectionStart 属性为 0。如果要测量 TLS 协商时间,则需要牢记这一点

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

一旦 DNS 查询和连接协商结束,与获取文档及其依赖资源相关的计时就会发挥作用。

请求和响应

加载性能受两种因素的影响

  • 外部因素: 这些因素包括延迟和带宽。除了选择托管公司和 可能的 CDN 之外,它们(在很大程度上)是我们无法控制的,因为用户可以从任何地方访问 Web。
  • 内部因素: 这些因素包括服务器和客户端架构,以及资源大小和我们优化这些因素的能力,这些都在我们的控制范围内。

这两种类型的因素都会影响加载性能。与这些因素相关的计时至关重要,因为它们描述了资源下载所需的时间。导航时间和资源时间都使用以下指标描述加载性能

  • fetchStart 标记浏览器开始获取资源(资源时间)或导航请求的文档(导航时间)的时间。这在实际请求之前,是浏览器检查缓存(例如,HTTP 和 Cache 实例)的点。
  • workerStart 标记请求开始在 Service Worker 的 fetch 事件处理程序中处理的时间。当没有 Service Worker 控制当前页面时,这将为 0
  • requestStart 是浏览器发出请求的时间。
  • responseStart 是响应的第一个字节到达的时间。
  • responseEnd 是响应的最后一个字节到达的时间。

这些计时使您可以衡量加载性能的多个方面,例如 Service Worker 中的缓存查找下载时间

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

您还可以衡量请求和响应延迟的其他方面

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

您可以进行的其他测量

导航时间和资源时间除了前面的示例概述的内容之外,还有其他用途。以下是一些其他情况,其中相关的计时可能值得探索

  • 页面重定向: 重定向是被忽视的增加延迟的来源,尤其是重定向链。延迟以多种方式增加,例如 HTTP 到 HTTPS 的跳转,以及 302/未缓存的 301 重定向。redirectStartredirectEndredirectCount 计时有助于评估重定向延迟。
  • 文档卸载:unload 事件处理程序中运行代码的页面中,浏览器必须先执行该代码,然后才能导航到下一页。unloadEventStartunloadEventEnd 测量文档卸载。
  • 文档处理: 除非您的网站发送非常大的 HTML 有效负载,否则文档处理时间可能无关紧要。如果您的网站属于这种情况,则 domInteractivedomContentLoadedEventStartdomContentLoadedEventEnddomComplete 计时可能值得关注。

如何在代码中获取计时

到目前为止显示的所有示例都使用 performance.getEntriesByType,但还有其他方法可以查询性能条目缓冲区,例如 performance.getEntriesByNameperformance.getEntries。当只需要进行简单的分析时,这些方法很好。但在其他情况下,它们可能会通过迭代大量条目,甚至重复轮询性能缓冲区以查找新条目,从而引入过多的主线程工作。

收集性能条目缓冲区中的条目的推荐方法是使用 PerformanceObserverPerformanceObserver 侦听性能条目,并在条目添加到缓冲区时提供它们

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

与直接访问性能条目缓冲区相比,这种收集计时的方法可能感觉很笨拙,但它比将主线程与不服务于关键且面向用户的目的的工作绑定在一起更可取。

如何“打电话回家”

收集完所有需要的计时后,您可以将它们发送到端点以进行进一步分析。执行此操作的两种方法是使用 navigator.sendBeaconfetch 并设置 keepalive 选项。这两种方法都将以非阻塞方式向指定端点发送请求,并且请求将排队,以便在需要时超出当前页面会话的生命周期

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // Caution: If you have lots of performance entries, don't
  // do this. This is an example for illustrative purposes.
  const data = JSON.stringify(performance.getEntries());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

在此示例中,JSON 字符串将以 POST 有效负载的形式到达,您可以根据需要解码、处理和存储在应用程序后端。

结论

收集到指标后,就由您来决定如何分析该现场数据。在分析现场数据时,应遵循一些通用规则,以确保您得出有意义的结论

  • 避免平均值,因为它们不能代表任何一个用户的体验,并且可能会被异常值扭曲。
  • 依赖于百分位数。在基于时间的性能指标数据集中,值越低越好。这意味着当您优先考虑低百分位数时,您只关注最快的体验。
  • 优先考虑值的长尾。当您优先考虑第 75 个百分位数或更高的体验时,您将重点放在应有的位置:最慢的体验。

本指南并非旨在成为有关导航时间或资源时间的详尽资源,而是一个起点。以下是一些您可能会觉得有用的其他资源

借助这些 API 及其提供的数据,您将能够更好地了解真实用户体验到的加载性能,这将使您更有信心地诊断和解决现场加载性能问题。