在现场调试性能

了解如何使用调试信息对性能数据进行归因,从而帮助您通过 Google Analytics(分析)识别和解决实际用户问题

Google 提供了两类用于衡量和调试性能的工具:

  • 实验室工具:使用 Lighthouse 等工具加载网页,此类工具会在模拟各种情况(例如,网速缓慢和低端移动设备)的模拟环境中加载。
  • 现场工具Chrome 用户体验报告 (CrUX) 等工具,该工具基于来自 Chrome 的汇总真实用户数据。(请注意,PageSpeed InsightsSearch Console 等工具报告的字段数据来自 CrUX 数据。)

虽然现场工具可提供更准确的数据(实际上可代表真实用户的体验),但实验室工具通常在帮助您识别和解决问题方面效果更好。

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

另一方面,Lighthouse 会发现问题并提出具体的改进建议。但是,Lighthouse 只会针对在网页加载时发现的性能问题提出建议。它不会检测出仅因用户互动(例如滚动或点击页面上的按钮)而显现的问题。

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

本文将详细介绍您可以使用哪些 API 针对当前每个 Core Web Vitals 指标收集额外的调试信息,并就如何在现有分析工具中捕获这类数据为您提供一些建议。

用于归因和调试的 API

Cumulative Layout Shift (CLS)

在所有核心网页指标中,CLS 可能是在现场收集调试信息对其最重要的指标。CLS 会在页面的整个生命周期内衡量,因此用户与页面互动的方式(滚动了多远、点击的内容等)会对是否存在布局偏移以及哪些元素发生偏移有重大影响。

请参考以下来自 PageSpeed Insights 的报告:

采用不同 CLS 值的 PageSpeed Insights 报告
PageSpeed Insights 会同时显示实测数据和实验数据(如果有),但它们可能会有所不同

实验室 (Lighthouse) 报告的 CLS 值与实际报告的 CLS 值(CrUX 数据)截然不同,因此,如果您认为该网页可能包含许多在 Lighthouse 中测试时没有使用的互动式内容,那么这样做是合理的。

但是,即使您了解了用户互动会影响字段数据,您仍然需要知道页面上的哪些元素发生了偏移,从而产生第 75 个百分位的得分为 0.28。LayoutShiftAttribution 接口可以帮您实现这一点。

获取布局偏移归因

LayoutShiftAttribution 接口在 Layout Instability 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});

针对发生的每一次布局偏移测量数据并将其发送到分析工具并不现实;不过,通过监控所有偏移,您可以跟踪最差的偏移并仅报告有关这些偏移的信息。

我们的目标不是找出并修正每位用户所发生的每一次布局偏移;而是找出影响最多用户的偏移,从而对网页的 CLS(第 75 个百分位)影响最大。

此外,您无需在每次发生变化时都计算最大的源元素,只有在准备好将 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) 以内!

下面列出了一些随最大偏移源元素一起捕获可能有用的其他元数据:

  • 发生最大转变的时间
  • 发生最大偏移时的网址路径(适用于动态更新网址的网站,例如单页应用)。

Largest Contentful Paint (LCP)

若要在字段中调试 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 子部分时间可能也会很有用,这有助于确定与您的网站相关的具体优化步骤。

Interaction to Next Paint (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 库包含一个可显示所有此类信息的归因 build,以及一些其他信号

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

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);
onFID(sendToGoogleAnalytics);
onINP(sendToGoogleAnalytics);

这段代码是 Google Analytics(分析)专用的,但大体上也适用于其他分析工具。

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

例如,如需调试 INP,您可能需要收集正与之互动的元素、互动类型、时间、loadState、互动阶段等(例如长动画帧数据)。

web-vitals 归因 build 会公开额外的归因信息,如下例中的 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);
onFID(sendToGoogleAnalytics);
onINP(sendToGoogleAnalytics);

如需查看公开的调试信号的完整列表,请参阅网页指标归因文档

报告并直观呈现数据

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

如前所述,您不一定需要解决用户遇到的每一个问题,而想要解决的是影响最多用户的问题,尤其是首先要解决的问题,这些问题也应该是对核心网页指标得分影响最大的问题。

对于 GA4,请参阅有关如何使用 BigQuery 查询和直观呈现数据的专用文章。

摘要

希望这篇博文对您概述了如何使用现有性能 API 和 web-vitals 库获取调试信息,从而根据现场实际用户访问情况来诊断性能。虽然本指南侧重于核心网页指标,但这些概念也适用于调试任何可在 JavaScript 中衡量的性能指标。

如果您刚刚开始衡量性能,并且已经是 Google Analytics(分析)用户,那么 Web Vitals 报告工具可能会是一个很好的切入点,因为它已经支持报告 Core Web Vitals 指标的调试信息。

如果您是分析服务供应商,并且想要改进自己的产品并向用户提供更多调试信息,请考虑本文中介绍的一些技巧,但不要局限于此处介绍的技巧。本文旨在普遍适用于所有分析工具;不过,各个分析工具可能(并且应该)捕获和报告更多调试信息。

最后,如果您认为由于 API 本身缺少功能或信息而无法调试这些指标,请向 web-vitals-feedback@googlegroups.com 发送反馈。