User Timing API

了解您的 Web 应用

Alex Danilo

高性能 Web 应用对于提供出色的用户体验至关重要。随着 Web 应用变得越来越复杂,了解性能影响对于打造极具吸引力的体验至关重要。在过去的几年里,浏览器中出现了许多不同的 API,以帮助分析网络性能、加载时间等,但这些 API 不一定能提供非常精细的详细信息和足够的灵活性,以找出拖慢应用的运行速度。不妨输入 User Timing API,此 API 提供了一种机制,可供您用来对 Web 应用进行插桩,从而确定应用将时间花在了何处。在本文中,我们将介绍此 API 以及使用示例。

无法衡量就无法优化的方面,就不能优化

要加快运行缓慢的 Web 应用的速度,第一步是弄清楚时间都花在了哪里。衡量 JavaScript 代码区域对时间的影响是确定热点的理想方式,也是寻找改善性能的第一步。幸运的是,借助 User Timing API,您可以在 JavaScript 的不同部分插入 API 调用,然后提取详细的计时数据,这些数据有助于您进行优化。

高分辨率时间和now()

精确时间测量的一个基本组成部分是精确率。以前,我们的时间以毫秒为主,这一点没关系,但构建无卡顿的 60 FPS 网站意味着每个帧需要在 16 毫秒内完成绘制。因此,如果精确率只有毫秒级,就会缺乏进行良好分析所需的精度。输入高分辨率时间,这是现代浏览器中内置的一种新时间类型。高分辨率时间可为我们提供浮点时间戳,其分辨率精确到微秒,比以前好一千倍。

如需获取 Web 应用中的当前时间,请调用 now() 方法,该方法构成了性能接口的扩展。以下代码展示了如何执行此操作:

var myTime = window.performance.now();

还有一个名为 PerformanceTiming 的接口,该接口提供了与 Web 应用的加载方式相关的多个不同时间。now() 方法会返回 PerformanceTiming 中出现 navigationStart 时间所经过的时间。

DOMHighResTimeStamp 类型

当您尝试为过去的 Web 应用设定时间时,应使用 Date.now() 之类的函数,它会返回 DOMTimeStamp。DOMTimeStamp 会返回一个以毫秒为单位的整数值作为其值。为了提供高分辨率时间所需的更高的准确度,我们推出了一种名为 DOMHighResTimeStamp 的新类型。此类型是一个浮点值,也会返回以毫秒为单位的时间。但由于它是浮点数,因此该值可以表示小数毫秒,因此可以产生精确到千分之一毫秒的精度。

User Timing 接口

现在,由于我们拥有高分辨率的时间戳,我们可以使用“User Timing”界面提取时间信息。

User Timing 接口提供了一些功能,可让我们在应用中的不同位置调用方法,从而提供 Hansel 和 Gretel 样式的面包屑导航路径,让我们能够跟踪所花费的时间。

使用 mark()

mark() 方法是时间分析工具包中的主要工具。mark() 的用途是为我们存储时间戳。mark() 的超级实用之处在于我们可以为时间戳命名,API 会将名称和时间戳作为一个单位来记住。

在应用的不同位置调用 mark() 可让您算出在 Web 应用中达到该“标记”所用的时间。

该规范列出了一些可能有趣且一目了然的商标名称,例如“mark_fully_loaded”“mark_fully_visible”“mark_above_the_fold”等。

例如,我们可以使用以下代码设置应用完全加载的标记:

window.performance.mark('mark_fully_loaded');

通过在整个 Web 应用中设置命名标记,我们可以收集大量时间数据,并在闲暇时进行分析,从而确定应用在何时在做什么。

使用 measure() 计算测量值

设置大量计时标记后,你会想要找出它们之间的间隔时间。为此,您需要使用 measure() 方法。

measure() 方法会计算标记之间经过的时间,还可以衡量标记与 PerformanceTiming 接口中任何知名事件名称之间的间隔时间。

例如,您可以使用如下代码计算出从 DOM 完成到应用状态完全加载所需的时间:

window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');

当您调用 measure() 时,它会独立于您设置的标记存储结果,以便您稍后检索它们。通过在应用运行时存储时间,应用可保持响应,并且您可以在应用完成一些工作后转储所有数据,以便稍后进行分析。

正在舍弃“clearMarks()”标记

有时,清除自己设置的一些标记非常有用。例如,您可能会在 Web 应用上进行批量运行,因此希望重新开始每次运行。

通过调用 clearMarks(),您可以轻松清除您已设置的任何标记。

因此,以下示例代码会取代您已有的所有现有标记,因此您可以根据需要再次设置计时运行。

window.performance.clearMarks();

当然,在某些情况下,您可能不想清除所有标记。因此,如果您想去除特定标识,只需传递要移除的标识的名称即可。例如,下面的代码:

window.peformance.clearMarks('mark_fully_loaded');

去掉我们在第一个示例中设置的标记,而保留我们设置的任何其他标记。

您可能还需要移除您所做的所有测量,有一个名为 clearMeasures() 的相应方法可以做到这一点。它的工作原理与 clearMarks() 完全相同,只不过前者适用于您所做的任何测量。例如,代码:

window.performance.clearMeasures('measure_load_from_dom');

将移除我们在上面的 measure() 示例中采取的措施。如果您要移除所有测量,其工作原理与 clearMarks() 相同,也就是说,您只需调用 clearMeasures() 而不使用任何实参。

获取超时数据

设定标记和测量区间无疑是件好事,但在某些情况下,您可能希望获取该时间的数据以执行一些分析。这也非常简单,您只需使用 PerformanceTimeline 接口即可。

例如,我们可以使用 getEntriesByType() 方法以列表形式获取所有标记时间,或者所有测量超时,以便对其进行迭代并提取数据。很棒的是,列表会按时间顺序返回,以便您按照标记在 Web 应用中的点击顺序查看标记。

以下代码:

var items = window.performance.getEntriesByType('mark');

将返回我们的 Web 应用程序中已遇到的所有标记的列表,而代码:

var items = window.performance.getEntriesByType('measure');

返回了我们所采取的措施的列表。

您也可以使用指定的特定名称来返回相应条目的列表。例如,代码:

var items = window.performance.getEntriesByName('mark_fully_loaded');

将返回一个列表,其中包含一项在 startTime 属性中包含“mark_full_loaded”时间戳的项。

为 XHR 请求计时(示例)

现在,我们对 User Timing API 有了充分的了解,接下来可以使用它来分析所有 XMLHttpRequests 在网页应用中花费的时间。

首先,我们将修改所有 send() 请求,以发出一个设置标记的函数调用,同时通过设置另一个标记的函数调用更改成功回调,然后生成对请求所用时间的测量。

通常,我们的 XMLHttpRequest 应如下所示:

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  do_something(e.responseText);
}
myReq.send();

在本示例中,我们将添加一个全局计数器来跟踪请求数量,并使用该计数器存储针对发出的每个请求的衡量指标。执行此操作的代码如下所示:

var reqCnt = 0;

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  window.performance.mark('mark_end_xhr');
  reqCnt++;
  window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
  do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();

上述代码会为我们发送的每个 XMLHttpRequest 生成一个具有唯一 name 值的测量。我们假设请求是按顺序运行,并行请求的代码需要稍微复杂一点,以处理不按顺序返回的请求。我们在这里让读者练习一下。

在 Web 应用完成大量请求后,我们可以使用以下代码将所有请求转储到控制台:

var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length; ++i) {
  var req = items[i];
  console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}

总结

User Timing API 提供了许多适用于 Web 应用各个方面的强大工具。在整个 Web 应用中分散 API 调用并对生成的时间数据进行后处理,以清楚地了解时间花在哪里,就可以轻松地缩小应用中的热点。但是,如果您的浏览器不支持此 API,该怎么办?没关系,您可以在此处找到一个超棒的 polyfill,它可以很好地模拟该 API,而且可与 webpagetest.org 配合使用。还在等什么呢?立即在您的应用中试用 User Timing API,您将找出如何提升其速度,您的用户会感谢您改善其体验。