一、前言
最近在做性能监控相关的工作,将准备工作中涉及的资料进行一次整理。
二、性能监控的两种方式
2.1、合成监控
合成监控(Synthetic Monitoring,SYN),就是在一个模拟场景里,去提交一个需要做性能审计的页面,通过一系列的工具、规则去运行你的页面,提取一些性能指标,得出一个审计报告。
合成监控工具比较有代表性的是 LightHouse,这张图是对掘金首页进行的一次性能测试报告:
2.2、真实用户监控
真实用户监控(Real User Monitoring,RUM),就是用户在我们的页面上访问,访问之时就会产生各种各样的性能指标,我们把这些性能指标上传到我们的日志服务器上,进行数据的提取清洗加工,最后在我们的监控平台上进行展示的一个过程。
目前已有的第三方产品有:OneApm、听云、FrontJS、NewRelic 等等。
2.3、对比
对比项 | 合成监控 | 真实用户监控 |
---|---|---|
实现难度及成本 | 较低 | 较高 |
采集数据丰富度 | 丰富 | 基础 |
数据样本量 | 较小 | 大(和业务量成正比) |
适合场景 | 支持团队自有业务,对性能做定性分析 ,或配合CI做小数据量的监控分析。 | 作为中台产品支持前台业务,对性能做定量分析,结合业务数据进行深度挖掘。 |
这两种方式具体的优缺点可以参考 蚂蚁金服如何把前端性能监控做到极致 这篇文章。
三、RIAL 模型
google 开发者提出了一种 RAIL 模型来衡量应用性能,即:Response、Animation、Idle、Load。
同时,Google 还提出了一系列性能指标,比如First Contentful Paint、Speed Index、Largest Contentful Paint 等等,具体这些指标的含义,可以参考 前端性能优化指南[7]--Web 性能指标 这篇文章。
四、基础指标定义&计算
可以根据 PerformanceTiming API 中的字段处理后得到以下关键指标:
描述 | 计算方式 | 备注 |
---|---|---|
First Paint Time,首次渲染时间(白屏时间)。 | fpt = responseEnd - fetchStart | 从请求开始到浏览器开始解析第一批HTML文档字节的时间差。 |
Time to Interact,首次可交互时间。 | tti = domInteractive - fetchStart | 浏览器完成所有HTML解析并且完成DOM构建,此时浏览器开始加载资源。 |
HTML加载完成时间, 即DOM Ready时间。 | ready = domContentLoadEventEnd - fetchStart | 如果页面有同步执行的JS,则同步JS执行时间=ready-tti。 |
页面完全加载时间 | load = loadEventStart - fetchStart | load=首次渲染时间+DOM解析耗时+同步JS执行+资源加载耗时。 |
首包时间 | firstbyte = responseStart - domainLookupStart | 无 |
其他指标:
描述 | 计算公式 | 备注 |
---|---|---|
DNS查询耗时 | dns = domainLookupEnd - domainLookupStart | 无 |
TCP连接耗时 | tcp = connectEnd - connectStart | 无 |
Time to First Byte(TTFB),请求响应耗时。 | ttfb = responseStart - requestStart | 参见Google Development定义。 |
内容传输耗时 | trans = responseEnd - responseStart | 无 |
DOM解析耗时 | dom = domInteractive - responseEnd | 无 |
资源加载耗时 | res = loadEventStart - domContentLoadedEventEnd | 表示页面中的同步加载资源 |
SSL安全连接耗时 | ssl = connectEnd - secureConnectionStart | 只在HTTPS下有效 |
五、其他性能数据
5.1、FPS 数据
检测页面卡顿:可以使用定时器,检测时间间隔,间隔大于预期的时候表示卡顿。
对于卡顿的检测可以参考 如何评价页面的性能 这篇文章。
5.2、长任务时间统计
主要是使用 PerformanceObserver 对 longtask 事件进行监听:
var observer = new PerformanceObserver((list) => {
var entries = list.getEntries();
for (var i = 0; i < entries.length; i++) {
console.log(JSON.stringify(entries[i], null, 4));
}
});
observer.observe({
entryTypes: ["longtask"]
});
输出示例:
{
"name": "self",
"entryType": "longtask",
"startTime": 1390.31999999861, // 此为 metric 上报时的时间
"duration": 999.0749999997206, // 该事件的耗时
"attribution": [
{
"name": "unknown",
"entryType": "taskattribution",
"startTime": 0,
"duration": 0,
"containerType": "window",
"containerSrc": "",
"containerId": "",
"containerName": ""
}
]
}
详细参数说明:Long Tasks API
不过,这个方法只能检测到 longtask,不能检测到具体是哪个方法或哪个模块。这个需要更详细的埋点数据去和 longtask 产生的时间去做匹配,才能发现具体的 longtask 在什么位置。
5.3、慢加载追踪
在页面 onload
后,可以利用 performance.getEntriesByType('resource')
获取资源加载时间,超过阈值的时候,进行上报。
5.4、浏览器内存
可使用 performance.memory
进行数据采集。
5.5、用户特征数据
比如ip、userAgent 等,这里只说一下 ip 获取的方式:
var dom = document.createElement('script');
dom.src = "http://pv.sohu.com/cityjson?ie=utf-8";
var container = document.head;
container.appendChild(dom);
dom.onload = function () {
container.removeChild(dom);
console.log(JSON.stringify(window.returnCitySN, null, 4))
}
dom.onerror = function () {
container.removeChild(dom);
}
输出示例:
{
"cip": "xxx.xxx.xxx.xxx",
"cid": "110000",
"cname": "北京市"
}
六、注意事项
需要注意监控对页面本身性能的影响。