web性能优化监控分析神器【Web Performance Timing API】

4,390 阅读10分钟

Web Performance Timing API

简介

想象一下您正在访问我们的W3C网站,如果Web内容在一定时间内没有显示在屏幕上,那么作为用户,也许您只是关闭选项卡,然后转到其他选项。但是,作为开发人员,您可能希望跟踪请求和导航详细信息中的提示,以便找出导致此网页速度下降的原因。

幸运的是,大多数浏览器供应商都愿意公开允许开发人员收集准确性能细节的性能特征。顾名思义,这些性能计时API有助于从不同方面识别Web应用程序的瓶颈并提高性能。

当试图了解Web应用程序的性能时,瀑布图可能是您会想到的第一个工具。您是否想过了解这些图形图表背后的魔力?在以下各节中,我们将解释一组性能监视API,这些API使您不仅可以执行瀑布图测量。

相关文章

资源时效【Resource Timing】

上图是一个简单网页每个加载资源的时间数据的瀑布图。 实际上,[RESOURCE-TIMING] API 可以提供比这更多的详细信息。 图2是开发人员能够访问的 Web 应用程序中每个加载资源的一组属性。

资源的加载时间【Load Times for Resources 】

Web 应用程序大多由一组可下载的资源组成。 这里的资源通常引用 HTML 文档、 XHR 对象、链接(例如样式表)或 SVG 元素。

Resources Timing 数据作为全局 window.performance 对象上的方法公开,我们将在下一节讨论这个问题。 建议您使用 performance.getEntriesByType("Resource")方法为每个请求的资源获取一个资源计时对象数组。

Performanceresourcetiming 接口扩展了 Performance Timeline 中的 PerformanceEntry 接口。 资源计时中的属性可以用 responseEndstartTime 之间的差值来计算所用的时间。

如图2和图3所示,您可以访问页面上每个资源的一组关键网络计时属性。

每个时间戳都以微秒为单位,由 High Resolution Time 规范中的 window.performance.now()方法提供。

例子【Example】

举个例子,让我们来测量一下获取 W3C 主页上的图标所需的时间。

<!doctype html>
<html>
  <head>
  </head>
  <body onload="loadResources()">
    <script>
       function loadResources()
       {
          var image1 = new Image();
          image1.onload = resourceTiming;
          image1.src = 'https://www.w3.org/Icons/w3c_main.png';
       }

       function resourceTiming()
       {
           var resourceList = window.performance.getEntriesByType("resource");
           for (var i = 0; i < resourceList.length; i++)
           {
              if (resourceList[i].initiatorType == "img")
              {
                 alert("End to end resource fetch: " + (resourceList[i].responseEnd - resourceList[i].startTime));
              }
           }
       }
    </script>
    <img id="image0" src="https://www.w3.org/Icons/w3c_home.png">
  </body>
</html>

浏览器兼容【Browser Support】

到目前为止,[RESOURCE-TIMING] API 已得到广泛实现。 图4显示了主要的桌面和移动网络浏览器的当前支持数据,请访问 caniuse.com 了解最新状态。

执行时间线【Performance Timeline】

概要【Overview】

您可能已经开始思考,这些网页上资源的计时数据是否足够好,以衡量其性能? 是的,为了理解你的 web 应用程序,总是有必要探索更多: 页面导航、用户交互、请求-响应循环等。 根据这一要求,Web 性能工作组引入了性能时间表,这是一个统一的接口,用于获取各种性能指标。

EntryType

Performance Timeline API 使用 PerformanceEntry.entryType 来描述这个 PerformanceEntry对象所表示的接口类型,它表示测算的性能(时间)。

var entryType = performance.getEntries()[0].entryType
// Entry Type

每个 PerformanceEntry 对象都公开以下继承的属性:

performance.getEntries

performance.getEntries用来获取当前时间调用前,浏览器从进入该页面开始的所有事件类型的详细时间数据,返回是一个Array<PerformanceEntry>

name(名称)

​ 这个属性的 DOMString 标识符对象,不一定是唯一的。

entryType(类型)

​ 描述由此表示DOMString接口类型的 PerformanceEntry对象。

startTime(开始时间)

​ 此性能指标的第一个记录时间戳的时间值。

duration(所用时间)

​ 此记录的整个事件持续时间的时间值

W3c WebPerf WG 维护 PerformanceEntry.entryType 的已知值列表。

entryType 的值

分析较长的用时【High-Resolution Time】

前言【Introduction】

参与 Performance Timeline 的所有指标都能够以亚毫秒级的分辨率提供计时数据,即 DOMHighResTimeStamp,它由高分辨率时间规范[ HR-TIME-2]定义。

单调计时【Monotonic Clock】

在设计的早期阶段,我们的计时 api 是根据墙上时钟的时间来定义的。 不幸的是,在现代计算机系统中,这样的时间有一个令人不快的特性: 它们并不是以时间实际流逝的速度单调地增长。 特别是,NTP 调整、用户配置更改等等会导致系统报告的时间向后、向前太快或向前太慢。

例如,可以在到 Date.now()的两个调用之间登录正数、负数或零。

var mark_start = Date.now();
doTask(); // Some task
if (window.console) window.console.log('Duration of task: ' + (Date.now() - mark_start));

High-Resolution Time的概念是提供一个单调的、均匀递增的时间戳,适用于区间测量,这是基于以下规则而实现的:

  • now()方法时返回的时间值和Performance 方法,工作表现对象具有相同时间起源【time origin】 必须不受系统时钟调整或系统时钟偏斜的影响。
  • now()方法返回的任意两个按时间顺序记录的时间值之间的差值,如果两个时间值具有相同的值,则永远不要为负数

亚毫秒分析【Sub-millisecond Resolution】

Now()在确定日历时间的当前值方面确实非常有用,而且使用历史悠久。 然而,从长远来看,需要更高的精确度。

图形就是一个例子。 在计算基于脚本的动画的帧速率时,开发人员需要亚毫秒的分辨率来确定一个动画是否在60 FPS 下绘制。 如果没有亚毫秒级的分辨率,开发人员只能确定动画是以58.8 FPS 还是62.5 FPS 绘制。

Domhighrestimestamp 类型和 Performance 接口的now()方法通过提供亚毫秒级分辨率的时间值来解决本节中总结的问题。

时间起源【Time Origin】

时间起源确定时间值从哪个时间被测量。 对于专用工作线程或文档,起始时间是该文档页面导航的开始时间,对于共享工作线程,起始时间是创建共享工作线程的时间。 为了更准确的定义,请检查规范的细节。

假设我们有一个共享工作线程 a,它是在父文档的导航开始后10ms 创建的。 如果在工作线程和父上下文中创建共享工作线程 a 之后,我们称之为 performance.now() 要用时5ms,那么我们应该看到以下值:

Shared worker A:
performance.now(): 5.000 ms

Parent context:
performance.now(): 15.000 ms

要在相同的时间线上显示这样的事件,应用程序可以使用 translateTime 方法从工作线上转换 DOMHighResTimeStamps

// ---- worker.js -----------------------------
// Shared worker script
onconnect = function(e) {
  var port = e.ports[0];
  port.onmessage = function(e) {
    // Time execution in worker
    var task_start = performance.now();
    result = runSomeWorkerTask();
    var task_end = performance.now();

    port.postMessage({
       'task': 'Some worker task',
       'start_time': task_start,
       'end_time': task_end,
       'result': result
    });
  }
}

// ---- application.js ------------------------
// Timing tasks in the document
var task_start = performance.now();
result = runSomeWorkerTask();
var task_end = performance.now();

plotEventOnTimeline({
  'task': 'Some document task',
  'start_time': task_start,
  'end_time': task_end,
  'result': result
});

// Translating worker timestamps into document's time origin
var worker = new SharedWorker('worker.js');
worker.port.onmessage = function (event) {
  var msg = event.data;

  // translate timestamps into document's time origin
  msg.start_time = performance.translateTime(msg.start_time, worker);
  msg.end_time = performance.translateTime(msg.end_time, worker);

  // plot the results on document's timeline
  plotEventOnTimeline(msg);
}

originTime 添加到 performance.now()中,就会得到一个在任何上下文中都可以比较的时间值。 现在我们可以得到这样的结果:

Shared worker A:
performance.now(): 5.000 ms
performance.originTime: 110.000 ms
performance.originTime + performance.now(): 115.000 ms

Parent context:
performance.now(): 15.000 ms
performance.originTime: 100.000 ms
performance.originTime + performance.now(): 115.000 ms

导航时间【Navigation Timing】

现在我们知道了如何获取单个资源的计时指标,接下来让我们进一步访问文档导航的完整计时信息。

导航时间、性能时间线和资源时间【Navigation Timing, Performance Timeline and Resource Timing】

导航是关于用户代理如何将请求的 HTMLCSSJavaScript 转换为渲染的像素,这是用户浏览文档最关键的步骤之一。

导航计时 API 是 Web 性能 API 的起点。 在[ NAVIGATION-TIMING ]中,通过访问 window.performance.navigation,您将获得一个 PerformanceNavigationTiming 实例,该实例提供有关页面性能的与时间相关的信息。 这不符合通过performance.getEntries 提供统一入口的目标。 到2011年引入性能时间轴 api 时,导航计时的初始设计已经被广泛实现,因此要将其与性能时间轴对齐为时已晚。

导航计时 API Level 2试图通过一个理想的导航计时设计来修复这个历史性的错误。 它参与 Performance Timeline API,并从 PerformanceResourceTiming 接口扩展 initialatortypeworkerStart

PerformanceNavigationTiming 的属性

图5显示了在[ NAVIGATION-TIMING-2]中定义的页面的导航的关键性能特征列表。

下面是浏览 www.w3.org 网页的演示。

浏览器兼容【Browser Support】

图7是由 caniuse 网站生成的支持数据表[ NAVIGATION-TIMING]。

用户时间【User Timing】

到目前为止,通过导航时序和资源时序,您可以充分访问资源加载和页面导航中那些关键时刻的时序信息。 但是,如果您想确定用户的按钮点击交互出了什么问题,该怎么办? 是否有可能获得对你来说很重要的个人任务的高精度性能特征?

User TimingPerformance 接口的扩展,可以通过提供高精度的时间戳帮助您度量应用程序的性能。

这里有一个简单的示例,解释了开发人员如何使用本文中定义的接口来获取与开发人员脚本相关的计时数据。

<!doctype html>
<html>
  <head>
    <title>User Timing example</title>
  </head>
  <body onload="init()">
    <script>
       function init()
       {
            performance.mark("startTask1");
            doTask1(); // Some developer code
            performance.mark("endTask1");

            performance.mark("startTask2");
            doTask2(); // Some developer code
            performance.mark("endTask2");

            measurePerf();
       }

       function measurePerf()
       {
           var perfEntries = performance.getEntriesByType("mark");
           for (var i = 0; i < perfEntries.length; i++)
           {
                 if (window.console) console.log("Name: "        + perfEntries[i].name +
                                                 " Entry Type: " + perfEntries[i].entryType +
                                                 " Start Time: " + perfEntries[i].startTime +
                                                 " Duration: "   + perfEntries[i].duration + "\n");
           }
       }
    </script>
  </body>
</html>

手动打点【performance.mark()】

Performancemark 接口扩展了 Performance 接口,并通过一个名为 mark()的函数向用户公开标记。

Mark()允许 web 开发人员在他们的 web 应用程序中创建独特的标记,并使用 DOMString markName 自定义标记。 例如,要创建一个名为before click 的标记,请调用 window.performance.mark("before click”) ;

您可以使用 Performance 接口访问已经存储的标记。 通过调用 window.performance.getEntriesByType(" mark") ,您将得到应用程序中所有标记的列表。

当标记不再具有任何价值时,可以删除它们。 您可以通过调用 clearMarks()来删除所有的标记,或者通过调用 clearMarks("mark to delete")来删除一个标记、标记要删除。

计算点之间的耗时【performance.measure()】

当您准备了足够的标记(两个点+)时,PerformanceMeasure 接口可以帮助您计算两个标记之间的运行时间。 此接口也是 Performance 接口的扩展,该接口公开通过 measure()方法创建的度量。 在这里,measure ()存储两个标记之间的 DOMHighResTimeStamp 持续时间以及相关的名称("measure")。 例如,要度量连续的单击,我们可以调用 window.performance.measure("measure click","click before","click after")

PerformanceMark 接口类似,只需调用 window.performance.getEntriesByType("measure")就可以获得度量值,并使用 clearMeasures()方法删除度量值。

浏览器兼容【Browser Support】

现在,你可以在大多数主流浏览器上使用 User Timing API。 请访问 caniuse 网站获取最新的浏览器支持信息。

应用场景

  • 检测 WEB 应用的各依赖资源的加载时间,并上报到服务器进行分析
  • 对耗时可能较长的方法函数进行耗时监听,并上传服务器进行分析
  • 分析用户从进入网页开始到页面呈现的所有时间,并且上传服务器分析首屏渲染耗时

当拿到上述的数据后,我们就可以对数据进行分析,知道我们的程序到底慢在哪里,然后做正对性的优化,而不像原来没有数据的支撑导致我们无从下手优化工作。

以上是我对英版原版文档的翻译,如果有翻译不对的地方还请谅解。

2020年4月1日

Jboss(基老板)