自己动手打造前端性能监控系统

自己动手打造前端性能监控系统了解前端性能监控的方式以及如何打造监控系统

自己动手打造前端性能监控系统

背景

为什么要做监控页面性能?

一个页面性能差的话会影响到很多方面。在公司层面,页面性能会影响公司收益,如用户打开页面等待的太久,用户可能会直接关掉页面,或者下次不再打开了,特别是在移动端用户对页面响应延迟容忍度很低。

除此之外,页面的加载速度还将直接影响页面的SEO,网页加载速度太慢,用户会直接关掉,这直接增加页面的跳出率,当搜索引擎发现页面跳出率高,搜索引擎会认为该网站对用户的价值不高,从而降低排名。2018年7月谷歌公司新规定,页面访问时间比较长,谷歌公式将会降低该页面的搜索排名。

虽然性能很重要,但在开发迭代中,开发会有所忽略,性能会随着版本迭代而有所衰减,所以我们需要一个性能监控系统,持续监控,评估,预警页面性能的状况,发现瓶颈,从而指导优化工作。

页面性能的评估与监控有很多成熟优秀的工具 ,比如gtmetrix 网站,他可以同时查多个分析工具的的结果,会提供许多的建议。

但这种方式与真实情况偏离,无法反馈某个地区的整体速度,慢速用户多少,无法反映性能的波动情况,另外除了白屏之类的,我们还有一些功能性的测速,比如页面可点击时间,广告展示的时间等等,这些都是无法模拟监控的。

为了持续监控不同网络环境下用户访问情况与页面各功能可用情况,我们选择在页面上植入js来监控线上真实用户数据。具体做法就是用一段代码将用户的数据上报到我们的服务器,通过一个系统将数据汇总,处理,最后图形化数据,方便我们查看各个页面等性能。

测速系统的设计

测试系统分三个部分,如下

  • 前端上报
    • 如何记录测速时间点。
    • 如何上报。
    • 数据的采样。
  • 数据处理,入库。
  • 数据展示

###前端上报

在前端植入一段前端js代码,通过这些代码来上报页面性能数据,那一般哪些指标能够更好的反馈用户的体验呢?

用户最大感受就是,页面为什么打开要等那么久,为什么图片加载那么慢,页面加载半天也不能点击。这些用户的感受对于程序员来说就是重要的页面性能指标。根据用户上述痛点抽象出指标,白屏时间,首屏时间,可交互时间。那么这个时间我们是如何统计的捏?

确定统计起始点

起始点时间,应该是我们输入网址后,点回车作为起始点,这样才是用户真正开始等待的时间。如果是高端的浏览器,我们可以直接使用Navigation Timeing接口来获取统计起点。

Navigation Timeing接口是一个在web中精准测量性能的javascript API,这个接口提供了一系列详细的时间状态。

在Chrome中打开控制台,在命令行中输入performance,点开并查看它的timing属性,你会看到如下代码

1.png

每一个performance.timing属性都表示一个页面事件(例如页面发送了请求)或者页面加载(例如当DOM开始加载),测量以毫秒的形式从1970年1月1日的午夜开始。结果为0表示该事件未发生(例如redirectEnd或者redirectStart等)

这里有一张从 Navigation Timing draft 弄来的 performance.timing 事件的顺序图。

2.png

其中navigationStart表示当浏览器请求的时间点,通俗地就是你在url输入栏里按下回车间的时间点,或则页面按F5刷新的时间点。

其他时间点的详细解释请点击 https://www.w3.org/TR/navigation-timing/,或则google一下,有多篇文章做了解释,这里就不再累述。

此接口大部分浏览器都已经支持,除了pc端ie9以下的浏览器。

白屏时间

用户看到页面展示出现一个元素的时间。很多人认为白屏时间是页面返回的首字节时间,但这样其实并不精确,因为头部资源还没加载完毕,页面也是白屏。

真正白屏结束的时间分为三种。

第一种没有靠js渲染的普通页面,白屏时间应该是在头部外链资源加载完,因为浏览器只要加载头部资源才会真正的渲染页面。所以白屏时间点最好是打印在头部末尾的位置(这里可能也不精确,但尽量保证接近),如代码所示。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!--头部资源-->
    <link href="style.css">
    <title>Document</title>
    <scirot>
        // 白屏幕结束时间
        var time = +new Date() - performance.timing.navigationStart;
    </scirot>
</head>
<body>
</body>
</html>

第二种,是使用了一些前端框架,比如vue,reacjs,他们需要执行完js后,才会将内容渲染到页面上,或者异步拉取数据,在数据拉回来在显示页面。这种情况下我们一般会在页面价格loading态。那么白屏结束时间在这个loading加载的后面。

首屏时间

首屏时间是指页面第一屏所有资源完整展示的时间。这个时间每个页面都不一致。比如一个页面的首屏是4张图片,那么我们应该在四张图片加载完成后才算首屏时间,或则页面是异步拉取数据的,首屏时间应该是将数据插入到浏览器的时间。总之找到首屏资源最后加载完成的时间点就是首屏时间。

上报方式

测量好时间后,就需要将数据发送给服务端。测速数据对丢失率要求比较低,且测速应该尽量在不影响主流程的逻辑和页面的性能前提下进行。使用的img标签get请求来上报数据,主要有以下原因。

  • 不存在ajax跨域问题,可做不同源的请求
  • 很古老的标签,没有浏览器兼容性问题
 var i = new Image();
 i.onload = i.onerror = i.onabort = function () {
 	i = i.onload = i.onerror = i.onabort = null;
 }
 i.src = url;
            

一些高级浏览器还支持 navigator.sendBeacon方法。这个方法可以用来发送一些小量数据,该方法是异步的,且在浏览器关闭请求也照样能发,特别适合上报统计的场景。

navigator.sendBeacon(url, data ? $.param(data) : null)

最终方案:当浏览器支持sendBeacon方法,优先使用该方法,不支持时使用img的方式上报。

采样

测速上报数据是海量的,由于数据太大,入库处理时间也会增加,且服务器性能有限,为了避免资源的浪费,在上报过程中进行数据采样处理。采样的粒度由用户端自己控制,如果采样为1/10, 也那么上报数据要加上rate=10, rate为采样率。

数据收集和入库

我们在一台机器上起了一个nginx服务器,nginx服务器可以记录访问,将用户的访问记录写进日志,这个日志可以记录请求的所有信息请求头,例如请求参数,请求ip。日志可以按照自己设定的格式来生成日志。

当页面的测速发送请求过来,nginx记录这个请求,将该请求写进日志中。

我们并没有用到nginx的logrotater(日志定时轮询)。由于logrotater最小颗粒度是1天,但我们希望日志是按5分钟一个文件来存储(原因是文件可以分批处理,避免一次性处理文件太大,且我们查询的测速点一天的走势的测速点颗粒度也是5分钟)。

nginx配置如下:

if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{1})[0-4]")
{
    set $logname $1-$2-$3-$4-$50;
}
if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{1})[5-9]")
{
    set $logname $1-$2-$3-$4-$55;
}

生成该日志:
access_log logs/stat.y.qq.com.sp.access.$logname.log spdata;

直接在日志文件生成的时候,就分割好。但这样有个缺点不能使用logrotate定时删除日志文件的功能,要额外写一个定时删除日志的脚本,避免文件过度沉淀,浪费磁盘资源。

log存的数据格式为:

log_format  spdata  '$time_local ~|^ $http_x_forwarded_for ~|^ $request ~|^ $http_referer ~|^ $status ~|^ $http_user_agent ~|^ $cookie_ptisp ~|^ $cookie_uin';

其中 ~|^ 为分隔符

统计的字段有

  • 时间,按5分钟
  • IP,用户的ip地址
  • data,页面上报的数据
    • appid 产品id产品id (比如QQ音乐, 全民K歌)
    • pid 项目id (比如PC客户端, YQQ, QQ音乐手机客户端, 其他H5)
    • pageid 页面id (项目下面的具体某个页面)
    • points 测速点  1=xx&2=xx&3=xx…
    • r 采样率 0~1
  • referer 页面referer
  • ua 解析出 分平台 分系统 分版本 分app 网络类型
  • isp 运营商
  • uin 用户的id

为了减少服务器的压力,上报机器与入库服务器分离。入库时,入库服务器定时从上报机器上拉取日志,进行数据入库。

数据的入库

数据的处理是该系统一大难题,全平台每天的pv上亿。为了避免数据过于庞大,我们将收集的数据按日期建立新表。

即使按日期建立新表,查询的数据也有上千万,直接查询表的数据也是非常耗时的。为了解决数据查询耗时的问题,我们建立了三个表,数据统计表,原始数据表,原始数据索引表

数据统计表

统计表是记录5分钟内某个页面所有点的平均耗时。在解析数据的时候,程序将一天分为多个5分钟,计算每个测速点的5分钟平均速度,并写进数据统计表,在查询某个测速点的一天的走势,我们可以直接查询统计表,无需将所有点再重新便利一遍。使用统计表可以大大减少查询的数据量,从而提高查询速度,查询mysql是毫秒级别。

原始表 & 索引表

数据统计表,可以解决大部分数据查询需要,但如果增加几个复合条件查询(查询条件有,国家,省份,运营商,网络类型,操作平台),显然统计表是满足不了的, 如果把每个条件组合都建立一个统计表,那会产生很多额外的表,而且复合查询使用率也不是很高,付出的却很大,这种方案是不可行的。

我们将原始数据分别存到不同的表里,通过索引表里面的索引来查原始数据。一个表的数据不宜过大,数据超过一定的数量级,查询速度会非常慢,为了保证Mysql的性能,这里建议单表记录数不超过1千万。通过索引来查询各个分表的数据。

阈值告警

在某个数据接口返回太慢而导致页面打开速度变慢,这个时候我们需要一个预警,来通知开发人员,在处理数据入库时,某个节点5分钟平均用时超过预设阈值,或者默认阈值10秒,系统会将这个信息以某种方式来告诉开发人员。页面通过告警能够非常敏感发现问题,从而及时解决,不让问题继续扩大。

数据展示

系统提供主要展示一个页面各个测速节点耗时的柱状图,单个测速点单天走势,和一段时间走势图。多维分析列表。

页面整体概况

3.png

来展示整个页面所有测速点耗时情况。

使用柱状图的原因,也是为了方便了页面开发人员,观察到底是哪个地方最为耗时,页面的瓶颈在哪。

除了查看页面的整体耗时,还可以查看单个测速点详细情况。

测速点详情

4.png

测速点将展示下面几个方面的信息

  • 平均耗时
  • 请求量
  • 慢速用户比例
  • 速度的正态分布

为了方便挖掘性能可能的瓶颈,需要从多维度对数据进行分析,比如移动端比较看重网络类型,需要我们根据网络类型对数据进行分析。

常见的维度的还有

  • 国家
  • 省份
  • 运营商
  • 网络类型
  • 操作系统

5.png

异常数据处理

6.png

在看观察点的走势图中,走势图会有一个很长的突刺。造成突刺的原因是这个点的延时要比周围点的数据高很多。为了搞清楚突刺的原因,查询了原始表,大多数上报点都是正常的,但会有一次上报的耗时是30多分钟,目前不知道为什么会上报这么长的渲染时间,可能跟用户机器有关,也可能跟当时网络情况有关。这些数据是用户端上报的,具体很难定位问题,这些点对算出来的图表平均值影响较大,为了保证数据整体正常,数据不受某一个异常节点影响太大,我们将大于10分钟的点过滤掉直接过滤掉。

总结

我们从三个各方面,前端上报,数据收集和入库,数据展示来介绍了如何打造一个测速系统,性能优化是我们需要持续关注,为了打造流畅的使用体验,测速系统是必不可少的工具。

参考

https://fex.baidu.com/blog/2014/05/build-performance-monitor-in-7-days/

https://www.qcloud.com/community/article/655542

http://javascript.ruanyifeng.com/bom/performance.html

今天的文章自己动手打造前端性能监控系统分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/15944.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注