前言
近年移动业务喷井式爆发,伴随着互联网人口红利的萎缩,用户更加青睐效率高体验优的站点,页面「到达」快慢直接影响了用户的体验,「性能」变得越来越重要。google 大数据统计观察发现,移动端用户对页面加载慢的容忍度远低于 PC 端,投放页面首屏加载时间从 2s 钟延迟到 3s 会造成 9.4% 的 PV 下降,8.3% 的跳出率增加以及 3.5% 的转化率下降。性能优化具有的商业价值不言而喻,总不能眼看着市场同事辛苦「求」来的用户在我们手中流失吧?这是身为一个合格前 yíng 台 bīn 不能允许的!盘他!
性能优化和监控的关系
有些公司同时想到的第一件事就是「优化」,网上搜一波性能优化的列表,把别人的「最佳实践」照搞一番,性能确实会有提升,但效率未必是最高的,反倒有些本末倒置,别人的问题未必就是自己的问题,「并行不代表因果」,要做性能优化,首先要做的是掌握详尽的性能指标信息,根据木桶原理,找到最拖后腿的指标「短板」,重点优化,将边际成本最小化。所以,首先我们要做的应该是性能指标的收集分析。
三种数据收集方式
指标收集主要分有三种方式:
1、本地模拟「Lab」
以 Google 的 Lighthouse 最为著名,它是一个开源的自动化工具,可以安装为 Chrome 的扩展插件,也可以命令行直接运行,它将针对目标页面运行一连串的测试,然后输出一个有关页面性能的评分报告。
根据此报告,可以有的放矢,逐一优化。
优点:
1. 评分报告全面且具有一定的权威性
2. 提供解决方案
3. 发现大的性能问题
复制代码
缺点:
1.存在「波动」,不同时刻的访客群体存在差异,数据只能反应当前时刻的「效果」
2.测试环境较单一,用户群体的环境各有不同,不可以一概之
复制代码
2、离线收集
将目标页面链接「委托」给第三方的服务,执行真实的访问指令,同时收集性能指标生成报表输出。
这种模式是在技术变现的背景下产生的,优秀的工具都是要付费的,当然开源和免费的优秀工具也很多,此前的阿里测就是其中一种,现已下线。停运的缘由我们不好揣测,此处谨向那些开源和提供免费服务的项目致敬!此处就不再多推荐了,以免有软文的嫌疑。
3、真实跟踪「Field 也叫 RUM」
在目标页面注入脚本,在约定的时机收集性能指标数据,统一上报数据中心,数据中心集中整合生成报表,再根据报表分析性能。
优点:
1. 数据全面,可采集到所有用户各个环境下的性能,生成直观的分布图
2. 数据真实,来源于真实用户
3. 反馈及时,优化后效果可及时地在报表上反馈出来
复制代码
缺点:
依赖较多,数据中心和脚本都需要自主开发,相对成本较高
复制代码
所谓监控,实际上就是性能「真实跟踪」,虽然依赖较多,但对性能指标的反馈最为真实有效。以下我们将围绕性能监控进行展开。
性能监控
如何去评价一个页面的性能好坏?
页面的加载有多快?加载时间为 X.XX 秒?
其实,好与坏,快与慢,都是很模糊的概念。我们常常听到别人介绍,「我们的页面白屏已经从 3s 优化到了 2s」,这种陈述的问题并不是不真实,而是在于扭曲了事实。 加载白屏时间会因用户不同而有很大的差异,具体取决于用户的设备性能以及网络状况。 我们不能单纯地以单个数字的形式呈现白屏时间而忽略加载时间过长的用户。更何况,收集来的数据具有幸存者偏差性。
下图是某页面统计来的在一定时间段内用户白屏时间分布直方图。可以直观反应出此页面的「性能」情况。
我们可以说,「90% 的用户可以在 3s 内完成页面加载」,但「均值」或「中位数」仅能反应此页面的某一点的表现,因为任何一个时间段都不能代表全部。
而性能优化的目标可以是增加 1s 内的用户比重,也可以是将后面的用户尽可能的集中在 1s-2s 之间。这些都是需要根据产品特点和目标灵活调整的。
需要监控哪些指标
我们所需要收集且重点关注的指标,应该是能准确反应用户体验的指标。
体验 | 表现 | 指标 |
---|---|---|
是否发生? | 导航是否成功启动?服务器是否有响应? | 首次绘制(FP)/首次内容绘制(FCP) |
是否有用? | 是否已渲染可以与用户互动的足够内容? | 首次有效绘制(FMP)/主角元素计时 |
是否可用? | 用户可以与页面交互,还是页面仍在忙于加载? | 可交互时间(TTI) |
是否令人愉快? | 交互是否顺畅而自然,没有滞后或卡顿? | 耗时较长的任务(在技术上不存在耗时较长的任务) |
以下时序屏幕截图直观地展示了用户体验点对应的指标,有助大家理解。
其中指标 FP 和 FCP 可以通过浏览器点 API 计算获取,而指标 FMP 和 TTI 由于并没有标准化的定义,因此也很难有标准化点 API 输出, 部分原因在于很难以通用的方式界定「有效」点。
除此关键指标之外,我们同时也需要关注基础指标,以便分析出造成关键指标「数据难看」的影响点。如 DNS 解析耗时、TCP 连接耗时、网络请求耗时以及资源加载耗时等。
标准化定义的指标
标准化定义的指标有以下几种
PerformanceTiming
Performance.timing 是一个只读属性,返回 PerformanceTiming 对象,该对象包括了页面相关的性能信息。
-
startTime(navigationStart):在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
-
unloadEventStart:前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
-
unloadEventEnd:和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
-
redirectStart:第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0
-
redirectEnd:最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内的重定向才算,否则值为 0
-
fetchStart:浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
-
domainLookupStart:DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
-
domainLookupEnd:DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
-
connectStart:HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
-
connectEnd:HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间
注意:这里握手结束,包括安全连接建立完成、SOCKS 授权通过 复制代码
-
secureConnectionStart:HTTPS 连接开始的时间,如果不是安全连接,则值为 0
-
requestStart:HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存,连接错误重连时,这里显示的也是新建立连接的时间
-
responseStart:HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
-
responseEnd:HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
-
domLoading:开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
-
domInteractive:完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件
注意:只是 DOM 树解析完成,这时候并没有开始加载网页内的资源 复制代码
-
domContentLoadedEventStart:DOM 解析完成后,网页内资源加载开始的时间,文档发生 DOMContentLoaded事件的时间
-
domContentLoadedEventEnd:DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕),文档的DOMContentLoaded 事件的结束时间
-
domComplete:DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
-
loadEventStart:load 事件发送给文档,也即 load 回调函数开始执行的时间,如果没有绑定 load 事件,值为 0
-
loadEventEnd:load 事件的回调函数执行完毕的时间,如果没有绑定 load 事件,值为 0
更多解释参见 W3C Recommendation - NavigationTiming 或 W3C Editor's Draft。
利用以上 API,我们可以计算出细颗粒度的性能基础指标
基础指标 | 描述 | 计算方式 | 备注 |
---|---|---|---|
rs | 准备新页面耗时 | fetchStart - navigationStart | |
rdc | 重定向时间 | redirectEnd - redirectStart | |
dns | DNS 解析耗时 | domainLookupEnd - domainLookupStart | |
tcp | TCP 连接耗时 | connectEnd - connectStart | |
ssl | SSL 安全连接耗时 | connectEnd - secureConnectionStart | 只在 HTTPS 下有效 |
ttfb | Time to First Byte(TTFB),网络请求耗时 | responseStart - requestStart | TTFB 有多种计算方式,ARMS 以 Google Development 定义为准 |
trans | 数据传输耗时 | responseEnd - responseStart | |
dom | DOM 解析耗时 | domInteractive - responseEnd | |
res | 资源加载耗时 | loadEventStart - domContentLoadedEventEnd | 表示页面中的同步加载资源 |
fbt | 首包时间 | responseStart - domainLookupStart | |
fpt | First Paint Time, 首次渲染时间 / 白屏时间 | responseEnd - fetchStart | 从请求开始到浏览器开始解析第一批 HTML 文档字节的时间差 |
tti | Time to Interact,首次可交互时间(非准确,仅做参考) | domInteractive - fetchStart | 浏览器完成所有 HTML 解析并且完成 DOM 构建,此时浏览器开始加载资源 |
load | 页面完全加载时间 | loadEventStart - fetchStart | load = 首次渲染时间 + DOM 解析耗时 + 同步 JS 执行 + 资源加载耗时 |
关于兼容性,可参考下图,绝大多数的浏览器已支持此 API,基本可以放心地使用在移动端
Paint Timing
相对于以上基础指标,跟用户感受关联最密切的可能就是各个「绘制(Paint)」时刻了,FP(First Paint),FCP(First Contentful Paint)以及 FMP(First Meaningful Paint)。
随着 SPA(单页面系统)的普及,单纯靠 PerformanceTiming 要准确地计算出各 Paint 的时间是很难的。庆幸是的,Chrome 60+ 带给我们一个全新的 API,Paint Timing,它提供了抓取「页面」和「资源」耗时的能力。此 API 尚处在实验阶段,并没有纳入 W3C 的标准,所以,也仅仅是 webkit 内核的高版本浏览器才支持。聊胜于无。
Performance.getEntries() 方法以数组形式对网页中每一个对象(脚本文件、样式表、图片文件等等)发出的请求进行统计记录,Paint 的就是其中的一种。我们可以使用方法 performance.getEntriesByType('paint')
轻松获得两个 PerformancePaintTiming 对象,对应的分别就是 FP 和 FCP。
更多关于 FP 和 FCP,可以参考此文 www.w3cplus.com/performance…
但关于 FMP,虽然 Chrome 的 Performance 工具已指示性地标出 FMP 的时间点,但依然未提供 API,部分缘由可能就是「无法标准化」吧。
关于 FMP
执行的时间点
由于 Performance 的赋能,我们没有必要在页面「最开始」就加载执行我们的监控脚本,再小的文件也会阻塞首屏的绘制,但是,如果有依赖关系,如一些算法依赖于监控 DOM 的变化,还是需要尽早进行初始化,所以时机应在 load 的前后,根据算法的需要进行调整,并没有一个准确的方案。
数据处理
数据收集到之后便是数据上报、处理
关于数据上报的成熟方案已有很多,「主动提交」,「反向代理」都可以,只要数据在不影响业务功能和性能的前提下完整地将上报到数据中心即可。
有了数据,我们就可以有目的性的将数据处理成我们需要的形式。
可以分析用户分布
横向比较,细查差异因果,总结经验。
也可以按时间纬度展开,嗅探时间或流量对性能的影响,也可以找到异常点,筛选出异常日志重点关注。
总之,可以将有限的数据玩出无限的可能!
以上,便是对性能监控的一些基础介绍,希望对你有所帮助。
[1] 以用户为中心的性能指标
转载请标明出处
作者: 木羽 zwwill
首发地址:zwwill/blog#31