performance
简介
Performance接口可以获取到当前页面与性能相关的信息。这个API为测量网站性能,提供以前没有办法做到的精度。
比如,为了得到脚本运行的准确耗时,需要一个高精度时间戳。传统的做法是使用Date对象的getTime方法。
1 | var start = new Date().getTime(); |
上面这种做法有两个不足之处。首先,getTime方法(以及Date对象的其他方法)都只能精确到毫秒级别(一秒的千分之一),想要得到更小的时间差别就无能为力了;其次,这种写法只能获取代码运行过程中的时间进度,无法知道一些后台事件的时间进度,比如浏览器用了多少时间从服务器加载网页。
为了解决这两个不足之处,ECMAScript 5引入“高精度时间戳”这个API,部署在performance对象上。它的精度可以达到1毫秒的千分之一(1秒的百万分之一),这对于衡量的程序的细微差别,提高程序运行速度很有好处,而且还可以获取后台事件的时间进度。
目前,所有主要浏览器都已经支持performance对象,包括Chrome 20+、Firefox 15+、IE 10+、Opera 15+。
performance.timing对象
performance对象的timing属性指向一个对象,它包含了各种与浏览器性能有关的时间数据,提供浏览器处理网页各个阶段的耗时。比如,performance.timing.navigationStart就是浏览器处理当前网页的启动时间。
1 | Date.now() - performance.timing.navigationStart |
上面代码表示距离浏览器开始处理当前网页,已经过了13260687毫秒。
下面是另一个例子。
1 | var t = performance.timing; |
上面代码依次得到页面加载的耗时、域名解析的耗时、TCP连接的耗时、读取页面第一个字节之前的耗时。
performance.timing对象包含以下属性(全部为只读):
- navigationStart:当前浏览器窗口的前一个网页关闭,发生unload事件时的Unix毫秒时间戳。如果没有前一个网页,则等于fetchStart属性。
- unloadEventStart:如果前一个网页与当前网页属于同一个域名,则返回前一个网页的unload事件发生时的Unix毫秒时间戳。如果没有前一个网页,或者之前的网页跳转不是在同一个域名内,则返回值为0。
- unloadEventEnd:如果前一个网页与当前网页属于同一个域名,则返回前一个网页unload事件的回调函数结束时的Unix毫秒时间戳。如果没有前一个网页,或者之前的网页跳转不是在同一个域名内,则返回值为0。
- redirectStart:返回第一个HTTP跳转开始时的Unix毫秒时间戳。如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0。
- redirectEnd:返回最后一个HTTP跳转结束时(即跳转回应的最后一个字节接受完成时)的Unix毫秒时间戳。如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0。
- fetchStart:返回浏览器准备使用HTTP请求读取文档时的Unix毫秒时间戳。该事件在网页查询本地缓存之前发生。
- domainLookupStart:返回域名查询开始时的Unix毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值。
- domainLookupEnd:返回域名查询结束时的Unix毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值。
- connectStart:返回HTTP请求开始向服务器发送时的Unix毫秒时间戳。如果使用持久连接(persistent connection),则返回值等同于fetchStart属性的值。
- connectEnd:返回浏览器与服务器之间的连接建立时的Unix毫秒时间戳。如果建立的是持久连接,则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束。
- secureConnectionStart:返回浏览器与服务器开始安全链接的握手时的Unix毫秒时间戳。如果当前网页不要求安全连接,则返回0。
- requestStart:返回浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的Unix毫秒时间戳。
- responseStart:返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的Unix毫秒时间戳。
- responseEnd:返回浏览器从服务器收到(或从本地缓存读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的Unix毫秒时间戳。
- domLoading:返回当前网页DOM结构开始解析时(即Document.readyState属性变为“loading”、相应的readystatechange事件触发时)的Unix毫秒时间戳。
- domInteractive:返回当前网页DOM结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的Unix毫秒时间戳。
- domContentLoadedEventStart:返回当前网页DOMContentLoaded事件发生时(即DOM结构解析完毕、所有脚本开始运行时)的Unix毫秒时间戳。
- domContentLoadedEventEnd:返回当前网页所有需要执行的脚本执行完成时的Unix毫秒时间戳。
- domComplete:返回当前网页DOM结构生成时(即Document.readyState属性变为“complete”,以及相应的readystatechange事件发生时)的Unix毫秒时间戳。
- loadEventStart:返回当前网页load事件的回调函数开始时的Unix毫秒时间戳。如果该事件还没有发生,返回0。
- loadEventEnd:返回当前网页load事件的回调函数运行结束时的Unix毫秒时间戳。如果该事件还没有发生,返回0。
根据上面这些属性,可以计算出网页加载各个阶段的耗时。比如,网页加载整个过程的耗时的计算方法如下:
1 | var t = performance.timing; |
performance.now()
performance.now方法返回当前网页自从performance.timing.navigationStart到当前时间之间的微秒数(毫秒的千分之一)。也就是说,它的精度可以达到100万分之一秒。
1 | performance.now() |
上面代码表示,performance.timing.navigationStart加上performance.now(),近似等于Date.now(),也就是说,Date.now()可以替代performance.now()。但是,前者返回的是毫秒,后者返回的是微秒,所以后者的精度比前者高1000倍。
通过两次调用performance.now方法,可以得到间隔的准确时间,用来衡量某种操作的耗时。
1 | var start = performance.now(); |
performance.mark()
mark方法用于为相应的视点做标记。
1 | window.performance.mark('mark_fully_loaded'); |
clearMarks方法用于清除标记,如果不加参数,就表示清除所有标记。
1 | window.peformance.clearMarks('mark_fully_loaded'); |
performance.getEntries()
浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个HTTP请求。performance.getEntries方法以数组形式,返回这些请求的时间统计信息,有多少个请求,返回数组就会有多少个成员。
由于该方法与浏览器处理网页的过程相关,所以只能在浏览器中使用。
1 | window.performance.getEntries()[0] |
上面代码返回第一个HTTP请求(即网页的HTML源码)的时间统计信息。该信息以一个高精度时间戳的对象形式返回,每个属性的单位是微秒(microsecond),即百万分之一秒。
performance.navigation对象
除了时间信息,performance还可以提供一些用户行为信息,主要都存放在performance.navigation对象上面。
它有两个属性:
performance.navigation.type
该属性返回一个整数值,表示网页的加载来源,可能有以下4种情况:
- 0:网页通过点击链接、地址栏输入、表单提交、脚本操作等方式加载,相当于常数performance.navigation.TYPE_NAVIGATENEXT。
- 1:网页通过“重新加载”按钮或者location.reload()方法加载,相当于常数performance.navigation.TYPE_RELOAD。
- 2:网页通过“前进”或“后退”按钮加载,相当于常数performance.navigation.TYPE_BACK_FORWARD。
- 255:任何其他来源的加载,相当于常数performance.navigation.TYPE_UNDEFINED。
performance.navigation.redirectCount
该属性表示当前网页经过了多少次重定向跳转。
计算一些性能数据
1 | //重定向次数: |
理解chrome的Resource Timing
时序图
请注意:当使用具有跨源资源的 Resource Timing API 时,确保所有资源具有 CORS 标头。
Resource Timing API 提供了与接收各个资源的时间有关的大量详细信息。请求生命周期的主要阶段包括:
- 重定向
- 立即开始 startTime。
- 如果正在发生重定向,redirectStart 也会开始。
- 如果重定向在本阶段末发生,将采集 redirectEnd。
- 应用缓存
- 如果是应用缓存在实现请求,将采集 fetchStart 时间。
- DNS
- domainLookupStart 时间在 DNS 请求开始时采集。
- domainLookupEnd 时间在 DNS 请求结束时采集。
- TCP
- connectStart 在初始连接到服务器时采集。
- 如果正在使用 TLS 或 SSL,secureConnectionStart 将在握手(确保连接安全)开始时开始。
- connectEnd 将在到服务器的连接完成时采集。
- 请求
- requestStart 会在对某个资源的请求被发送到服务器后立即采集。
- 响应
- responseStart 是服务器初始响应请求的时间。
- responseEnd 是请求结束并且数据完成检索的时间。
在 DevTools 中查看
要查看 Network 面板中给定条目完整的耗时信息,您有三种选择。
- 将鼠标悬停到 Timeline 列下的耗时图表上。这将呈现一个显示完整耗时数据的弹出窗口。
- 点击任何条目并打开该条目的 Timing 标签。
- 使用 Resource Timing API 从 JavaScript 检索原始数据。
此代码可以在 DevTools 的 Console 中运行。 它将使用 Network Timing API 检索所有资源。 然后,它将通过查找是否存在名称中包含“style.css”的条目对条目进行过滤。 如果找到,将返回相应条目。
Queuing
如果某个请求正在排队,则指示:
- 请求已被渲染引擎推迟,因为该请求的优先级被视为低于关键资源(例如脚本/样式)的优先级。 图像经常发生这种情况。
- 请求已被暂停,以等待将要释放的不可用 TCP 套接字。
- 请求已被暂停,因为在 HTTP 1 上,浏览器仅允许每个源拥有六个 TCP 连接。
- 生成磁盘缓存条目所用的时间(通常非常迅速)
Stalled/Blocking
请求等待发送所用的时间。 可以是等待 Queueing 中介绍的任何一个原因。 此外,此时间包含代理协商所用的任何时间。
Proxy Negotiation
与代理服务器连接协商所用的时间。
DNS Lookup
执行 DNS 查询所用的时间。 页面上的每一个新域都需要完整的往返才能执行 DNS 查询。
Initial Connection / Connecting
建立连接所用的时间,包括 TCP 握手/重试和协商 SSL 的时间。
SSL
完成 SSL 握手所用的时间。
Request Sent / Sending
发出网络请求所用的时间。 通常不到一毫秒。
Waiting (TTFB)
等待初始响应所用的时间,也称为至第一字节的时间。 此时间将捕捉到服务器往返的延迟时间,以及等待服务器传送响应所用的时间。
Content Download / Downloading
接收响应数据所用的时间。
诊断网络问题
通过 Network 面板可以发现大量可能的问题。查找这些问题需要很好地了解客户端与服务器如何通信,以及协议施加的限制。
已被加入队列或已被停止的系列
最常见问题是一系列已被加入队列或已被停止的条目。这表明正在从单个网域检索太多的资源。在 HTTP 1.0/1.1 连接上,Chrome 会将每个主机强制设置为最多六个 TCP 连接。如果您一次请求十二个条目,前六个将开始,而后六个将被加入队列。最初的一半完成后,队列中的第一个条目将开始其请求流程。
要为传统的 HTTP 1 流量解决此问题,您需要实现域分片。也就是在您的应用上设置多个子域,以便提供资源。然后,在子域之间平均分配正在提供的资源。
HTTP 1 连接的修复结果不会应用到 HTTP 2 连接上。事实上,前者的结果会影响后者。 如果您部署了 HTTP 2,请不要对您的资源进行域分片,因为它与 HTTP 2 的操作方式相反。在 HTTP 2 中,到服务器的单个 TCP 连接作为多路复用连接。这消除了 HTTP 1 中的六个连接限制,并且可以通过单个连接同时传输多个资源。
至第一字节的漫长时间
又称:大片绿色
等待时间长表示至第一字节的时间 (TTFB) 漫长。建议将此值控制在 200 毫秒以下。长 TTFB 会揭示两个主要问题之一。
请执行以下任一操作:
- 客户端与服务器之间的网络条件较差,或者
- 服务器应用的响应慢
要解决长 TTFB,首先请尽可能缩减网络。理想的情况是将应用托管在本地,然后查看 TTFB 是否仍然很长。如果仍然很长,则需要优化应用的响应速度。可以是优化数据库查询、为特定部分的内容实现缓存,或者修改您的网络服务器配置。很多原因都可能导致后端缓慢。您需要调查您的软件并找出未满足您的性能预算的内容。
如果本地托管后 TTFB 仍然漫长,那么问题出在您的客户端与服务器之间的网络上。很多事情都可以阻止网络遍历。客户端与服务器之间有许多点,每个点都有其自己的连接限制并可能引发问题。测试时间是否缩短的最简单方法是将您的应用置于其他主机上,并查看 TTFB 是否有所改善。
达到吞吐量能力
又称:大片蓝色
如果您看到 Content Download 阶段花费了大量时间,则提高服务器响应或串联不会有任何帮助。首要的解决办法是减少发送的字节数。