-
Notifications
You must be signed in to change notification settings - Fork 316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
2018你应该知道的Web性能信息采集指南 #25
Comments
不知道你们试过没有,得到的依旧是系统本地时间。 |
@Storm4542 这个地方之前确实写的不太对,已经改好啦。非常感谢~ 😊😊 |
很细致 |
有个问题,如何收集页面首屏渲染指标呢,是用文中的PerformancePaintTiming呢还是用PerformanceNavigationTiming里边的各种属性计算呢,网上其他文章很多都是根据Performance Timing API也就是PerformanceNavigationTiming计算的,然后就不知道怎么样了 |
这个今天刚看到了,可以获取到FCP、LCP、FID、CLS、TTFB指标,但是有浏览器兼容性问题。 |
博主,如何监控http api 的响应时间呢?目前的系统的感觉 ajax 请求,响应很慢。但是不知道如何把这些慢的api 找出来。 |
这个可以监控http api 的响应时间吗? 文档里没看到有说。目前的系统的感觉 ajax 请求,响应很慢。但是不知道如何把这些慢的api 找出来。 |
2018你应该知道的Web性能信息采集指南
假设您正在访问一个网站,如果Web内容不在几秒内显示在屏幕上,那么作为用户您可能会选择关闭标签页,转去浏览其他页面从而代替这个网页的内容。但是作为Web开发者,您可能希望跟踪请求与导航的详细信息,这样你就可以知道为什么这个网页的速度在变慢。
W3C性能工作组提供了可以用来测量和改进Web应用性能的用户代理(User Agent)特性与API。开发者可以使用这些API来收集精确的性能信息,从不同方面找出Web应用的性能瓶颈并提高应用的性能。
1. 如何获得高精度的时间?
ECMA-262 规范中定义了
Date
对象来表示自 1970 年 1 月 1 日以来的毫秒数。它足以满足大部分需求,但缺点是时间会受到时钟偏差与系统时钟调整的影响。时间的值不总是单调递增,后续值有可能会减少或者保持不变。例如,下面这段代码计算出来的“
duration
”有可能被记录为正数、负数或零。上面这段代码获取的持续时间“duration”并不精准,它会受到时钟偏差与系统时钟调整的影响,所以最终得到的“duration”可能为正数、负数或零,我们根本不知道它记录的时间究竟是不是正确的时间。
高精度时间(High Resolution Time,简称
hr-time
)规范定义了Performance
对象,通过Performance
对象我们可以获得高精度的时间。Performance
对象包含方法now
和属性timeOrigin
:方法
now
被执行后会返回从timeOrigin
到现在的高精度时间。属性
timeOrigin
返回页面浏览上下文第一次被创建的时间。如果全局对象为WorkerGlobalScope
,那么timeOrigin
为worker被创建的时间。例如,当
timeOrigin
的值被确定之后,无论将系统时间设置到什么时间,下面代码始终返回timeOrigin
最初被赋予的时间:如果两个时间值拥有相同的时间起源(Time Origin),那么使用
performance.now
方法返回的任意两个按时间顺序记录的时间值之间的差值永远不可能是负数。例如,下面这段代码计算出来的“
duration
”永远不可能为负数。通过
performance.timeOrigin
+performance.now
可以得到精准的当前时间。该时间不受时钟偏差与系统时钟调整的影响。2. 性能时间线(Performance Timeline)
在介绍如何获取性能指标之前,我们需要先介绍“性能时间线”,它提供了统一的接口来获取各种性能相关的度量数据。它是本文即将要介绍的其他获取性能指标方法的基础。
“性能时间线”本身并不提供任何性能信息,但它会提供一些方法,当您想要获得性能信息时,可以使用“性能时间线”提供的方法来得到您想获取的性能信息。
2.1 扩展
Performance
对象“性能时间线”扩展了
Performance
对象,新增了一些用于从“性能时间线”中获取性能指标数据的属性与方法。下表给出了在
Performance
对象上新增的方法:表中给出了三个方法,使用这些方法可以得到一个列表,列表中包含一系列用于承载各种性能数据的对象。换句话说,使用这些对象可以得到我们想要获得的各种性能信息。
在术语上这个列表叫做
PerformanceEntryList
,而列表中的对象叫做PerformanceEntry
。不同方法的过滤条件不同,所以列表中的
PerformanceEntry
对象所包含的数据也不同。2.2
PerformanceEntry
对象“性能时间线”定义了
PerformanceEntry
对象,该对象承载了各种性能相关的数据。下表给出了PerformanceEntry
对象所包含的属性:PerformanceEntry
对象的标识符,不唯一PerformanceEntry
对象的类型从上表中可以发现,“性能时间线”并没有明确定义
PerformanceEntry
对象应该返回什么具体内容,它只是定义了一个格式,返回的具体内容会根据我们获取的性能数据类型的不同而不同。本文的后面我们会详细介绍。2.3
PerformanceObserver
“性能时间线”还定义了一个非常重要的接口用来观察“性能时间线”记录新的性能信息,当一个新的性能信息被记录时,观察者将会收到通知。它就是
PerformanceObserver
。例如,可以通过下面代码定义一个长任务观察者:上面这段代码使用
PerformanceObserver
注册了一个长任务观察者,当一个新的长任务性能信息被记录时,回调会被触发。回调函数会接收到两个参数:第一个参数是一个列表,第二个参数是观察者实例。
在术语上这个列表被称为
PerformanceObserverEntryList
,并且包含三个方法getEntries
、getEntriesByType
、getEntriesByName
。可以通过这三个方法获得PerformanceEntryList
列表。这三个方法功能于使用方式均与前面介绍的相同。3. 如何收集“资源加载”相关性能度量数据?
获取资源加载相关的时间信息可以让我们知道我们的页面需要让用户等待多久。下面这段简单的JavaScript代码尝试测量加载资源所需的时间:
虽然这段代码可以测量资源的加载时间,但它不能获得资源加载过程中各个阶段详细的时间信息。同时这段代码并不能投放到生产环境,因为它有很多问题:
@import url()
和background: url()
加载的资源应该如何测量计时信息?link
、img
、script
xmlhttprequest
请求的,如何测量资源的计时信息?fetch
方法请求的资源如何测量计时信息?beacon
发送的请求如何测量计时信息?幸运的是,W3C性能工作组定义了资源计时(Resource Timing)规范让Web开发者可以获取非常详细的资源计时信息。
下面这个例子可以获取更加详细的资源计时信息:
上面代码通过
performance.getEntriesByType
方法得到一个列表,这个列表就是我们前面介绍的PerformanceEntryList
,并过滤出所有类型为resource
的PerformanceEntry
对象。类型为
resource
的PerformanceEntry
对象在术语上被称为PerformanceResourceTiming
对象。PerformanceResourceTiming
对象扩展了PerformanceEntry
对象并新增了很多属性用于获取详细的资源计时信息,PerformanceResourceTiming
对象的所有属性与其对应的作用如下表所示:PerformanceResourceTiming
对象的entryType
属性永远返回字符串“resource”redirectStart
属性相同,其他情况该属性将与fetchStart
相同responseEnd
与startTime
之间的时间workerStart
属性返回开始获取资源的时间,否则返回0Response
信息的时间(开始接受Response
的第一个字节,例如HTTP/2的帧头或HTTP/1.x的Response状态行)由于有一些属性功能比较复杂,下面将针对一些功能比较复杂的属性详细介绍。
3.1
initiatorType
简单来说
initiatorType
属性返回的内容代表资源是从哪里发生的请求行为。initiatorType
属性会返回下面列表中列出的字符串中的其中一个:url()
指令发出的,例如@import url()
或background: url()
3.2
domainLookupStart
准确的说,
domainLookupStart
属性会返回下列值中的其中一个:domainLookupStart
的值与fetchStart
相同domainLookupStart
等于开始从域信息缓存中检索域数据的时间3.3
domainLookupEnd
domainLookupEnd
属性会返回下列值中的其中一个:domainLookupStart
相同,如果使用了持久连接(persistent connection),或者从相关应用缓存(relevant application cache)或本地资源中获取资源,那么domainLookupEnd
的值与fetchStart
相同domainLookupEnd
为从域信息缓存中检索域数据结束时的时间3.4 过程模型
下图给出了
PerformanceResourceTiming
对象定义的时序属性。当从不同来源获取资源时,括号中的属性可能不可用。用户代理可以在时间点之间执行内部处理。图1 PerformanceResourceTiming 过程模型
4. 如何收集“网页加载”相关性能度量数据?
精准地测量Web应用的性能是使Web应用更快的一个重要方面。虽然利用JavaScript提供的能力可以测量用户等待时间(我们常说的埋点),但在更多情况下,它并不能提供完整或详细的等待时间。例如,下面的JavaScript使用了一个非常天真的方式尝试测量页面完全加载完所需要的时间:
上面的代码将计算在执行
head
标签中的第一行JavaScript之后加载页面所需的时间,但是它没有提供任何有关从服务端获取页面所需的时间信息,或页面的初始化生命周期。对于这种需求,W3C性能工作组定义了Navigation Timing规范,该规范定义了
PerformanceNavigationTiming
接口,提供了更有用和更准确的页面加载相关的时间数据。包括从网络获取文档到在用户代理(User Agent)中加载文档相关的所有时间信息。对于上面那个例子,使用Navigation Timing可以很轻松的用下面的代码做到并且更精准:
上面代码通过
performance.getEntriesByType
方法得到一个列表,这个列表就是我们前面2.1节介绍的PerformanceEntryList
,并过滤出所有类型为navigation
的PerformanceEntry
对象。类型为
navigation
的PerformanceEntry
对象在术语上被称为PerformanceNavigationTiming
对象。PerformanceNavigationTiming
对象扩展了PerformanceEntry
对象,通过该对象提供的duration
属性可以得到页面加载所消耗的全部时间。通过该
PerformanceNavigationTiming
对象可以获得页面加载相关的非常精准的时间信息:0
loadEventEnd
的时间减去startTime
的时间)PerformanceNavigationTiming
对象扩展了PerformanceResourceTiming
对象,所以PerformanceNavigationTiming
对象具有PerformanceResourceTiming
对象的所有属性,但是某些属性的返回值略有不同:同时 NavigationTiming 新增了一些属性,下面列表给出了新增的属性:
document.readyState
设置为complete
的时间type
属性的四种取值情况:location.reload()
方法prerender
的方式启动一个页面4.1 过程模型
图2给出了
PerformanceNavigationTiming
对象的时序属性。当页面从不同来源获取时,括号中的属性可能不可用。图2 PerformanceNavigationTiming 过程模型
从图2可以看出完整的页面加载时间信息包含很多信息。前端渲染相关的时间只占用很少的一部分(图2最后面两个蓝色部分
processing
与onLoad
)。这也是为什么我们在一开始说使用JS埋点的方式去测量页面加载时间很天真。5. 使用高精度时间戳来度量Web应用的性能
Web开发者需要一种能够**“评估与理解”**其Web应用性能的能力。虽然JavaScript提供了测量应用性能的能力(使用
Date.now()
方法获取当前时间戳),但这个时间戳的精度在不同的用户代理下存在一定的差异,并且时间会受到系统时钟偏差与调整的影响。W3C性能工作组定义了User Timing规范,提供了高精度且单调递增的时间戳,使开发者可以更好地测量其应用的性能。
下面代码显示了开发者应该如何使用User Timing规范定义的API来获得执行代码相关的时间信息。
5.1 关于User Timing
User Timing规范扩展了
Performance
对象,并在Performance
对象上新增了四个方法:5.1.1 mark方法
mark方法接收一个字符串类型的参数(mark名称),用于创建并存储一个
PerformanceMark
对象。更通俗的说,mark方法用于记录一个与名称相关时间戳。PerformanceMark
对象存储了4个属性:performance.now()
方法的返回值)下面代码展示了如何使用
mark
方法:当使用
mark
方法存储了一个PerformanceMark
对象后,可以通过前面介绍的getEntriesByName
方法得到一个列表,列表中包含一个PerformanceMark
对象。代码如下:5.1.2 clearMarks方法
顾名思义,
clearMarks
方法的作用是删除所有给定名称的时间戳数据(PerformanceMark
对象)。clearMarks
方法接收一个字符串类型的参数(mark名称),例如:上面代码使用
mark
方法记录了一个名为testName
的时间戳信息(存储了PerformanceMark
对象),随后使用clearMarks
方法清除名为testName
的时间戳信息,最后尝试获取名为testName
的时间戳信息时得到的是一个空列表。5.1.3 measure方法
虽然
mark
方法可以记录时间戳信息,但是获得两个mark
之间的持续时间还是有点麻烦,我们需要先获取两个PerformanceMark
对象,然后再执行减法。针对这个问题User Timing规范提供了
measure
方法,该方法的作用是使用一个名字将两个PerformanceMark
对象之间所持续的时间存储起来。measure
方法的参数:与
mark
方法相同,measure
方法会创建一个PerformanceMeasure
对象并存储起来。PerformanceMeasure
对象存储了4个属性:PerformanceMark
对象的startTime
属性,如果没有提供startMark
参数,则为0PerformanceMark
对象的startTime
属性的差值,可能是负数。下面代码展示了如何使用
measure
方法检测代码执行所持续的时间:5.1.4 clearMeasures方法
与
clearMarks
类似,clearMeasures
方法的作用是使用参数中提供的名称来删除PerformanceMeasure
对象。6. 如何知道“用户觉得网页慢”(如何检测长任务)?
保证UI的流畅很重要,那么如何检测UI是否流畅呢?
根据RAIL性能模型提供的信息,如果Web应用在100毫秒内的时间可以响应用户输入,则用户会觉得应用的交互很流畅。如果响应超过100毫秒用户就会感觉到应用有点轻微的延迟。如果超过1秒,用户的注意力将离开他们正在执行的任务。
由于JavaScript是单线程的,所以当一个任务执行时间过长,就会阻塞UI线程与其他任务。对于用户来说,他通常会看到一个“锁定”的页面,浏览器无法响应用户输入。
那么如何检测应用是否存在“长任务”呢?
一个已知的方式是使用一个短周期定时器,并检查两次调用之间的时间,如果两次调用之间的时间大于定时器的周期时间,那么很有可能有一个或多个“长任务”延迟了定时器的执行。
这种方式虽然可以实现需求,但它并不完美。它要不停的轮询去检查长任务,在移动端对手机电池寿命不友好,并且也没有办法知道是谁造成了延迟(例如:自己的代码 vs 第三方的代码)。
W3C性能工作组提供了Long Tasks规范,该规范定义了一个接口,使Web开发者可以监测“长任务”是否存在。
使用案例:
上面的代码注册了“长任务”观察器,它的功能是每当有超过50毫秒的任务被执行时调用回调函数。
2.3节介绍了
PerformanceObserver
,所以回调函数中的变量perfEntries
保存了一个列表,列表中包含了所有承载了长任务数据的对象。承载了长任务数据的对象在术语上被称为
PerformanceLongTaskTiming
。PerformanceLongTaskTiming
对象中保存了长任务相关的信息,包括以下属性:same-origin-ancestor
)same-origin-ancestor
相反,如果当前页面注册了一个长任务观察者并iframe了一个其他页面,这时候iframe中如果存在长任务,则当前页面的长任务观察者会收到通知,这时候name属性的值为same-origin-descendant
)name
属性为multiple-contexts
)Time Origin
的时间TaskAttributionTiming
对象,该对象有以下属性:7. 如何收集“首屏渲染”相关性能度量数据?
加载并不是一个单一的时刻,它是一种体验,没有任何一种指标可以完全捕获。事实上在页面加载期间有多个时刻可以影响用户将其视为“快”还是“慢”。
首次绘制(FP,全称First Paint)是第一个比较关键的时刻,其次是首次内容绘制(FCP,全称First Contentful Paint)。
这两个性能指标之间的主要区别在于“首次绘制”是当浏览器首次开始渲染任何可以在视觉上让屏幕发生变化的时刻。相比之下“首次内容绘制”是当浏览器首次从DOM中渲染内容的时刻,内容可以是文本,图片,SVG,甚至是canvas元素。
图3 首屏渲染指标
可以通过下面代码获得首屏渲染性能指标数据:
通过上面这行代码可以得到一个列表。列表中包含一个或两个
PerformancePaintTiming
对象。这取决于“首次内容绘制”是否存在。如图4所示:图4. 获取首屏渲染指标
从图3可以看到
PerformancePaintTiming
对象包含四个属性,这四个属性的值为:time origin
的我们可以使用下面的代码注册一个绘制观察器:
8. 总结
本文详细介绍了在Web应用中采集性能信息所需要的一些方法。其中包括:获得不受时钟偏差与系统时钟调整影响的高精度时间的方法、收集“页面资源加载”相关的性能度量数据的方法、收集“网页加载”相关的性能度量数据的方法、使用高精度时间戳在应用程序中埋点的方法、监测用户觉得网页“慢”的方法以及采集首屏渲染性能指标的方法。
The text was updated successfully, but these errors were encountered: