前端监控实践——FMP的智能获取算法
今天来给大家介绍下前端监控中一个特定指标的获取算法,有人会问,为啥就单单讲一个指标?这是因为,目前大部分的指标,比如白屏时间,dom加载时间等等,都能通过现代浏览器提供的各种api去进行较为精确的获取,而今天讲的这个指标,以往获取他的方式只能是通过逻辑埋点去获取它的值,因此在做一些前端监控时,需要根据业务需要去改变页面对这个值的埋点方式,会比较繁琐,恰巧最近刚刚好在做一些前端监控相关的项目,遇到这个问题时就在想,能不能通过一种无须埋点的方式,将这个值给获取到?倒腾了一段时间,终于把算法弄出来了,今天就来给大家介绍下————FMP(first meaning paint) 指标的智能获取算法
什么是FMP
解答这个问题之前,我们先来了解下现代前端监控性能的主要指标统计方法,在2013年之后,标准组织推出了 performance timing api
,如下图
这个api统计了浏览器从网址开始导航到 window.onload
事件触发的时间点,比如请求开始的时间点——requestStart
,响应结束的时间点——responseEnd
,通过这些时间点我们可以计算出一些对页面加载质量有指导意见的时长,比如以下几个:
- TTFB : ResponseStart - RequestStart (首包时间,关注网络链路耗时)
- FPT : ResponseEnd - FetchStart (首次渲染时间 / 白屏时间)
- TTI : DomInteractive - FetchStart (首次可交付时间)
- Ready : DomContentLoadEventEnd - FetchStart (加载完成时间)
- Load : LoadEventStart - FetchStart (页面完全加载时间)
通过这些指标我们可以得到很多有用的web端网页加载信息,建立对网页性能概况
以上的指标可以对网页进行数值化的衡量,但是其实这种衡量只能体现一个视角的性能观点,比如TTFB很快,就能代表用户能够很快的看到页面的内容嘛?这个不一定是成立的,因此人们有开始从用户的视角去分析网页加载的性能情况,将用户看待加载过程,分成了以下几个阶段:
- 页面是否正在正常加载 (happening)
- 页面加载的内容是否已经足够(useful)
- 页面是否已经可以操作了 (usable)
- 页面是否可以交互,动画是否顺畅(delightful)
而我们今天讨论的FMP(first meaningful paint)
,其实就是回答 is it useful
,加载的内容是否已经足够,其实这是一个很难被定义的概念。每个网页都有自己的特点,只有开发者和产品能够比较确定哪个元素加载的时间点属于FMP
,今天我们就来讨论一下,如何比较智能的去找出页面那个主要的元素,确定页面的FMP
成为FMP元素的条件
首先我们可以看看下面的图:
我们可以发现在页面中比较useful
的内容,都是含有信息量比较丰富的,比如图片,视频,动画,另外就是占可视面积较大的,页面中还存在两种形态的内容可以被视为是useful
的,一种是单一的块状元素,另外一种是由多个元素组合而成的大元素,比如视频元素,banner图,这种属于单一的块状元素,而像图片列表,多图像的组合,这种属于元素组合
总结一下成为FMP元素的条件:
- 体积占比比较大
- 屏幕内可见占比大
- 资源加载元素占比更高(img, svg , video , object , embed, canvas)
- 主要元素可能是多个组成的
算法如何设计
前面介绍了FMP
的概念还有成为FMP
的条件,接下来我们来看看如何设计FMP
获取的算法,按照上面的介绍,我们知道算法分为以下两个部分:
- 获取
FMP元素
- 计算
FMP元素
的加载时间
如果有了解过浏览器加载原理的同学都知道,浏览器在在获取到html页面之后会逐步的对html文档进行解析,遇到javascript会停止html文档的解析工作,执行javascript,执行完继续解析html,直到整个页面解析完成为止。页面除了html文档中的元素加载,可能在执行javascript的时候,会产生动态的元素片段加载,一般来说,首屏元素会在这期间加载。因此我们只需要监控元素的加载和加载的时间点,然后再进行计算。
具体的算法流程如下图
相关的代码链接我已经放在最后面了,下面我会逐步的讲解整个算法流程
我把整个流程分为两个下面两个部分:
- 监听元素加载,主要是为了确定普通元素加载的时间点
- 确定
FMP元素
,计算出最终的FMP
值
下面我们按照步骤来分析
初始化监听
- 可以看到首先我们先执行了
firstSnapshot
方法,用于记录在代码执行之前加载的元素的时间点 - 接下来初始化
MutationObserver
,开始监听document
的加载情况,在发生回调的时候,记录下当前到performance.timing.fetchStart
的时间间隔,然后对body的元素进行深度遍历,进行打点,记录是在哪一次回调的时候记录的,如下图
- 监听的最后我们会将在
window.onload
的时候去触发检查是否停止监听的条件,如下图
如果监听的时间超过LIMIT
,或者发生回调的时间间隔已经超过1s中,我们认为页面已经稳定,停止dom元素加载的监听,开始进入计算过程
完成监听,进行元素得分计算
- 首先前面我们说了,我们的元素对于页面的贡献是不同的,资源加载的元素会对用户视觉感官的影响比较大,比如图片,带背景的元素,视频等等,因此我设计了一套权重系统,如下:
可以看到svg
,img
的权重为2,canvas
,object
,embed
,video
的权重为4,其他的元素为1,
也就是说,如果一个图片面积为1/2首屏面积,其实他的影响力会和普通元素占满首屏的影响力一样
- 接着我们回到代码,我们首先会对整个页面进行深度优先遍历搜索,然后对每一个元素进行进行分数计算,如下图
可以看到我们通过element.getBoundingClientRect
获取了元素的位置和大小,然后通过计算"width * height * weight * 元素在viewport的面积占比"
的乘积,确定元素的最终得分,然后将改元素的子元素得分之和与其得分进行比较,去较大值,记录得分元素集
通过计算确定FMP
元素,计算最终FMP
时间
通过上面的步骤我们获取到了一个集合,这个集合是"可视区域内得分最高的元素的集合",我们会对这个集合的得分取均值,然后过滤出在平均分之上的元素集合,然后进行时间计算
可以看到分为两种情况去处理:
weight
为1的普通元素,那么我们会通过元素上面的标记,去查询之前保存的时间集合,得到这个元素的加载时间点weight
不为1的元素,那么其实就存在资源加载情况,元素的加载时间其实是资源加载的时间,我们通过performance.getEntries
去获取对应资源的加载时间,获取元素的加载速度
最后去所有元素最大的加载时间值,作为页面加载的FMP
时间
最后
以上就是整个算法的比较具体的流程,可能有人会说,这个东西算出来的就是准确的么?这个算法其实是按照特征分析,特定的规则总结出来的算法, 总体来说还是会比较准确,当然web页面的布局如果比较奇特,可能是会存在一些偏差的情况。也希望大家能够一起来丰富这个东西,为FMP
这个计算方法提出自己的建议
附上代码 链接