调试实际环境中的性能

了解如何使用调试信息归因您的性能数据,以帮助您使用分析识别和修复真实用户问题

Google 提供了两种类别的工具来衡量和调试性能

  • 实验室工具: 诸如 Lighthouse 之类的工具,在这些工具中,您的页面在模拟环境中加载,该环境可以模拟各种条件(例如,慢速网络和低端移动设备)。
  • 实际环境工具: 诸如 Chrome 用户体验报告 (CrUX) 之类的工具,这些工具基于来自 Chrome 的汇总的真实用户数据。(请注意,PageSpeed InsightsSearch Console 等工具报告的实际环境数据来源于 CrUX 数据。)

虽然实际环境工具提供更准确的数据(实际代表真实用户体验的数据),但实验室工具通常更擅长帮助您识别和修复问题。

CrUX 数据更能代表您页面的真实性能,但了解您的 CrUX 分数不太可能帮助您弄清楚如何提高性能。

另一方面,Lighthouse 会识别问题并为如何改进提出具体建议。但是,Lighthouse 只会为它在页面加载时发现的性能问题提出建议。它不会检测到仅在用户交互(例如滚动或单击页面上的按钮)时才会显现的问题。

这就提出了一个重要问题:如何从实际环境中的真实用户那里捕获核心 Web 指标或其他性能指标的调试信息?

这篇文章将详细解释您可以使用哪些 API 来收集当前每个核心 Web 指标的额外调试信息,并为您提供有关如何在您现有的分析工具中捕获此数据的想法。

用于归因和调试的 API

累积布局偏移 (CLS)

在所有核心 Web 指标中,CLS 可能是最需要在实际环境中收集调试信息的指标。CLS 是在页面的整个生命周期内衡量的,因此用户与页面交互的方式(他们滚动多远、点击什么等等)会对是否存在布局偏移以及哪些元素正在偏移产生重大影响。

考虑以下来自 PageSpeed Insights 的报告

A PageSpeed Insights Report with different CLS values
PageSpeed Insights 显示了可用的实际环境和实验室数据,这些数据可能不同

与实际环境(CrUX 数据)的 CLS 相比,实验室(Lighthouse)报告的 CLS 值差异很大,如果您考虑到页面可能有很多在 Lighthouse 中测试时未使用的交互式内容,这是有道理的。

但即使您理解用户交互会影响实际环境数据,您仍然需要知道页面上的哪些元素正在偏移,从而导致第 75 百分位数的得分为 0.28。LayoutShiftAttribution 接口使之成为可能。

获取布局偏移归因

LayoutShiftAttribution 接口在 布局不稳定性 API 发出的每个 layout-shift 条目上公开。

有关这两个接口的详细说明,请参阅 调试布局偏移。对于这篇文章的目的,您需要知道的主要内容是,作为开发人员,您能够观察页面上发生的每个布局偏移以及正在偏移的元素。

以下是一些示例代码,用于记录每个布局偏移以及偏移的元素

new PerformanceObserver((list) => {
  for (const {value, startTime, sources} of list.getEntries()) {
    // Log the shift amount and other entry info.
    console.log('Layout shift:', {value, startTime});
    if (sources) {
      for (const {node, curRect, prevRect} of sources) {
        // Log the elements that shifted.
        console.log('  Shift source:', node, {curRect, prevRect});
      }
    }
  }
}).observe({type: 'layout-shift', buffered: true});

为发生的每个布局偏移测量数据并将其发送到您的分析工具可能不切实际;但是,通过监视所有偏移,您可以跟踪最严重的偏移,并仅报告有关这些偏移的信息。

目标不是识别和修复每个用户发生的每个布局偏移;目标是识别影响最大数量用户的偏移,从而对页面在第 75 百分位数的 CLS 贡献最大。

此外,您无需在每次发生偏移时都计算最大的源元素,您只需在准备将 CLS 值发送到您的分析工具时才这样做。

以下代码获取已促成 CLS 的 layout-shift 条目列表,并从最大偏移中返回最大的源元素

function getCLSDebugTarget(entries) {
  const largestEntry = entries.reduce((a, b) => {
    return a && a.value > b.value ? a : b;
  });
  if (largestEntry && largestEntry.sources && largestEntry.sources.length) {
    const largestSource = largestEntry.sources.reduce((a, b) => {
      return a.node && a.previousRect.width * a.previousRect.height >
          b.previousRect.width * b.previousRect.height ? a : b;
    });
    if (largestSource) {
      return largestSource.node;
    }
  }
}

一旦您确定了促成最大偏移的最大元素,您就可以将其报告给您的分析工具。

给定页面的 CLS 最大贡献元素可能因用户而异,但如果您汇总所有用户的这些元素,您将能够生成一个偏移元素列表,这些元素影响的用户数量最多。

在您识别并修复了这些元素的偏移根本原因后,您的分析代码将开始报告较小的偏移作为您页面的“最差”偏移。最终,所有报告的偏移都将足够小,以至于您的页面完全在 “良好”阈值 0.1 之内!

一些其他元数据可能有助于与最大偏移源元素一起捕获,包括

  • 最大偏移发生的时间
  • 最大偏移发生时的 URL 路径(对于动态更新 URL 的网站,例如单页应用程序)。

最大内容渲染 (LCP)

要在实际环境中调试 LCP,您需要的主要信息是哪个特定元素是该特定页面加载的最大元素(LCP 候选元素)。

请注意,LCP 候选元素很可能(事实上,这很常见)因用户而异,即使对于完全相同的页面也是如此。

发生这种情况可能有几个原因

  • 用户设备具有不同的屏幕分辨率,这会导致不同的页面布局,从而导致不同的元素在视口中可见。
  • 用户并非总是加载滚动到最顶部的页面。通常,链接将包含 片段标识符,甚至 文本片段,这意味着您的页面有可能在页面上的任何滚动位置加载和显示。
  • 内容可能会为当前用户个性化,因此 LCP 候选元素可能会因用户而异。

这意味着您不能假设哪个元素或哪些元素集将是特定页面的最常见的 LCP 候选元素。您必须根据真实用户行为来衡量它。

识别 LCP 候选元素

要在 JavaScript 中确定 LCP 候选元素,您可以使用 Largest Contentful Paint API,这是您用来确定 LCP 时间值的同一 API。

在观察 largest-contentful-paint 条目时,您可以通过查看最后一个条目的 element 属性来确定当前的 LCP 候选元素

new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];

  console.log('LCP element:', lastEntry.element);
}).observe({type: 'largest-contentful-paint', buffered: true});

一旦您知道 LCP 候选元素,您就可以将其与指标值一起发送到您的分析工具。与 CLS 一样,这将帮助您确定哪些元素最重要,需要优先优化。

除了 LCP 候选元素之外,测量 LCP 子部分时间 也可能有用,这可以帮助确定哪些特定的优化步骤与您的网站相关。

与下次绘制的交互 (INP)

要在实际环境中捕获的 INP 最重要的信息是

  1. 与哪个元素进行了交互
  2. 交互的类型是什么
  3. 交互何时发生

慢速交互的一个主要原因是主线程被阻塞,这在 JavaScript 加载时很常见。了解大多数慢速交互是否发生在页面加载期间有助于确定需要采取哪些措施来解决问题。

INP 指标考虑了交互的完整延迟,包括运行任何已注册事件侦听器所需的时间以及在所有事件侦听器运行后绘制下一帧所需的时间。这意味着对于 INP,真正有用的是知道哪些目标元素倾向于导致慢速交互,以及这些交互的类型是什么。

以下代码记录了 INP 条目的目标元素和时间。

function logINPDebugInfo(inpEntry) {
  console.log('INP target element:', inpEntry.target);
  console.log('INP interaction type:', inpEntry.name);
  console.log('INP time:', inpEntry.startTime);
}

请注意,此代码未显示如何确定哪个 event 条目是 INP 条目,因为该逻辑更复杂。但是,以下部分说明了如何使用 web-vitals JavaScript 库获取此信息。

与 web-vitals JavaScript 库一起使用

前面的部分提供了一些通用建议和代码示例,用于捕获调试信息以包含在您发送到分析工具的数据中。

自版本 3 以来,web-vitals JavaScript 库包含一个 归因构建,该构建公开了所有这些信息,以及一些其他信号

以下代码示例显示了如何设置一个额外的 事件参数(或 自定义维度),其中包含一个调试字符串,该字符串有助于识别性能问题的根本原因。

import {onCLS, onINP, onLCP} from 'web-vitals/attribution';

function sendToGoogleAnalytics({name, value, id, attribution}) {
  const eventParams = {
    metric_value: value,
    metric_id: id,
  }

  switch (name) {
    case 'CLS':
      eventParams.debug_target = attribution.largestShiftTarget;
      break;
    case 'LCP':
      eventParams.debug_target = attribution.element;
      break;
    case 'INP':
      eventParams.debug_target = attribution.interactionTarget;
      break;
  }

  // Assumes the global `gtag()` function exists, see:
  // https://developers.google.com/analytics/devguides/collection/ga4
  gtag('event', name, eventParams);
}

onCLS(sendToGoogleAnalytics);
onLCP(sendToGoogleAnalytics);
onINP(sendToGoogleAnalytics);

此代码特定于 Google Analytics,但一般思路也应适用于其他分析工具。

此代码还仅显示如何报告单个调试信号,但能够收集和报告每个指标的多个不同信号是有用的。

例如,要调试 INP,您可能需要收集交互的元素、交互类型、时间、loadState、交互阶段以及更多信息(例如 Long Animation Frame 数据)。

web-vitals 归因构建公开了额外的归因信息,如以下 INP 示例所示

import {onCLS, onINP, onLCP} from 'web-vitals/attribution';

function sendToGoogleAnalytics({name, value, id, attribution}) {
  const eventParams = {
    metric_value: value,
    metric_id: id,
  }

  switch (name) {
    case 'INP':
      eventParams.debug_target = attribution.interactionTarget;
      eventParams.debug_type = attribution.interactionType;
      eventParams.debug_time = attribution.interactionTime;
      eventParams.debug_load_state = attribution.loadState;
      eventParams.debug_interaction_delay = Math.round(attribution.inputDelay);
      eventParams.debug_processing_duration = Math.round(attribution.processingDuration);
      eventParams.debug_presentation_delay =  Math.round(attribution.presentationDelay);
      break;

    // Additional metric logic...
  }

  // Assumes the global `gtag()` function exists, see:
  // https://developers.google.com/analytics/devguides/collection/ga4
  gtag('event', name, eventParams);
}

onCLS(sendToGoogleAnalytics);
onLCP(sendToGoogleAnalytics);
onINP(sendToGoogleAnalytics);

有关公开的调试信号的完整列表,请参阅 web-vitals 归因文档

报告和可视化数据

一旦您开始收集调试信息以及指标值,下一步就是汇总所有用户的数据,开始寻找模式和趋势。

如前所述,您不一定需要解决用户遇到的每个问题,您想要解决(尤其是在最初)影响最大数量用户的问题,这些问题也应该是对您的核心 Web 指标得分产生最大负面影响的问题。

对于 GA4,请参阅关于 如何使用 BigQuery 查询和可视化数据 的专门文章。

总结

希望这篇文章能够帮助您概述可以使用现有性能 API 和 web-vitals 库来获取调试信息的具体方法,以帮助诊断基于实际用户访问的实际环境中的性能。虽然本指南侧重于核心 Web 指标,但这些概念也适用于调试任何可在 JavaScript 中衡量的性能指标。

如果您刚开始衡量性能,并且已经是 Google Analytics 用户,那么 Web Vitals Report 工具可能是一个不错的起点,因为它已经支持报告核心 Web 指标的调试信息。

如果您是分析供应商,并且您希望改进您的产品并为您的用户提供更多调试信息,请考虑此处描述的一些技术,但不要将自己限制在此处提出的想法上。这篇文章旨在普遍适用于所有分析工具;但是,个别分析工具可能可以(并且应该)捕获和报告更多调试信息。

最后,如果您认为由于 API 本身缺少功能或信息而导致您调试这些指标的能力存在差距,请将您的反馈发送至 web-vitals-feedback@googlegroups.com