【大前端攻城狮之路】百度爱番番前端性能监控体系方案设计
一、背景
爱番番大前端整体面临以下问题:
- Metrics:URL的RED指标不全。URL不全,ERROR缺失,Duration分位置缺失。整体实效性为T+1。无法及时感知问题。只对基本页面级别的读操作进行了监控。
- Tracing:Trace无法全端串联,直接影响具体case的跟进。无前端Trace。
- Logging:无Log。Sentry的Error信息目前仅PC接入。且未进行实际使用;和trace无法打通。
- 报警:对于异常数据缺乏有效报警。
二、目标
- Metrics目标:RED指标,自定义Metrics
- Tracing目标:全端单动作追踪
- Logging目标:Error级别Log具备。单Traceid和Log能够实现串联
2.1 核心目标
- 页面级别性能监控。可包含条件检索和正常页面刷新。包含RED
- 能够基于用户id进行任意一次全端调用链追踪。
- 可分析一个操作流任意阶段之间的统计性耗时。
- 准实时呈现。数据延时小于5min。
- 可区分来源、地域、设备等核心信息
- 写请求性能监控。包含新建、编辑、删除。包含RED
2.2 目标抽象
AggrEvent、Event、Trace、Span
四个概念。Transaction用来做纯埋点,此处暂不需要。
- 带TraceID(可被覆盖)和TimeStamp信息的Event
- 全端Trace下的调用链。本质上是前端的Event链路 + 服务端的Span链路串联而成。
- 考虑到批量传输性能。采用AggrEvent进行聚合发送。
三、 名词解释
- RED:
RED方法是Weave Cloud在基于Google的“4个黄金指标”的原则下结合Prometheus以及Kubernetes容器实践,细化和总结的方法论,特别适合于云原生应用以及微服务架构应用的监控和度量。主要关注以下三种关键指标:
- (请求)速率:服务每秒接收的请求数。
- (请求)错误:每秒失败的请求数。
- (请求)耗时:每个请求的耗时。
- optid
(operate-id):一个操作唯一对应的一个id。比如一次刷新,一次修改。 - reqid
(request-id):单次请求唯一对应的一个id。如果重试,则reqid设置为新id。一个optid可能会存在多个reqid。 - tid
(tracing-id):贯穿服务端端调用链的唯一id。 - optType: 某一类操作定义。比如一次刷新,一次修改。
四、目标拆解
4.1 Why
4.1.1 为什么要做大前端监控体系?
4.1.2 收益是什么?
- Metrics:对各端各页面各场景RED指标准实时分析、可视化呈现,打破前端监控黑盒
- Tracing:打通前后端调用链,能够针对任意case进行前后端链路分析,让前端性能优化有的放矢
- Logging:对各端运行时错误进行实时监控报警,能通过日志最大程度还原用户现场并定位问题,提升前端页面稳定性
4.2 What
4.2.1 监控什么端?
4.2.2 监控什么方向
(1) 数据监控
- PV/UV:PV(page view),即页面浏览量或点击量。UV:指访问某个站点或点击某条新闻的不同IP地址的人数
- 用户在每一个页面的停留时间
- 用户通过什么入口来访问该网页
- 用户在相应的页面中触发的行为
(2) 性能监控
- 不同用户,不同机型和不同系统下的首屏加载时间
- 白屏时间
- http等请求的响应时间
- 静态资源整体下载时间
- 页面渲染时间
- 页面交互动画完成时间
(3) 异常监控
4.2.3 监控什么内容
类别 |
内容 |
页面分析 |
具体丰富的页面指标,提供如服务器端响应时间、网络延时、DOM解析和页面渲染时间等性能指标 帮助研发更快捷的定位服务端、客户端的页面问题 |
Ajax请求 |
获取用户访问过程,页面发出的所有Ajax请求URL、引用页面URL,监控某一Ajax请求的响应时间、回调时间、上传数据量、下载数据量以及响应过程中服务器返回的错误 |
JS error |
代码级定位出错页面或者脚本URL,引用页面URL,出错的行列信息、堆栈等信息,通过sourceMap定位源码文件,通过pageId,operateId以及tid最大限度还原上下文 监测Web App中JS错误的数量,各浏览器出错百分比和JS错误率随时间变化的趋势 |
浏览器监测 |
统计不同版本浏览器和浏览器类型的平均页面加载时间和吞吐率 提供基于多平台浏览器性能分析,兼容性分析 |
慢页面追踪 |
抓取加载时间超过设定阈值的页面上的元素信息,及每个元素的TCP建连、首包及剩余包等所需时间 详细定位页面上的哪些元素的加载拖慢了页面的响应,为优化用户体验提供依据 |
四、方案调研
自建or接入其他平台?
- 收费
- 无法与部门现有后端APM体系打通
- 无法满足特定case分析需求
结论:复用神策埋点SDK存储,上报能力及通路,进行SDK二次封装;自建日志服务及展示系统。
五、方案设计 (web)
1、采集
接口文档
指标 |
统计方式 |
白屏 |
用户浏览器输入网址后至浏览器出现文字或1px图片所花费时间。计算规则:优先使用Chrome、IE提供的firstPaintTime,没有获取到计算head中link、script脚本下载的最长时间。 |
首屏 |
用户浏览器首屏内所有的元素呈现所花费时间。计算规则:寻找首屏区域内的所有图片,计算最长加载时间得到首屏时间。 |
可交互 |
功能可以使用的时间,也指domready时间。计算公式:可交互=Navigation Timing API domContentLoadedEventStart – fetchStart。 |
完全加载 |
页面完全加载总时间。指从NavigationStart事件开始到LoadEventEnd事件结束,计算公式:LoadEventEnd-NavigationStart |
HTML加载 |
指主HTML文件从DNS解析到加载完且不包含排队时间和应用服务器响应时间,即包含DNS,TCP建连,Request和Response,计算公式:responseEnd-domainlookupStart-排队时间-应用服务器响应时间 |
页面渲染 |
指从responseEnd事件开始到loadEventEnd结束,包含DOM解析和资源加载,计算公式:LoadEventEnd-responseEnd |
DOM解析 |
指从responseEnd事件开始到DomContentLoadedEventEnd事件结束,计算公式:DomContentLoadedEventEnd-responseEnd |
资源加载 |
指从DomContentLoadedEventEnd事件开始到loadEventEnd事件结束,计算公式:loadEventEnd-DomContentLoadedEventEnd |
JS错误率 |
出现JS错误的比例。JS错误包含Javascript错误代码和位置信息。 |
服务端响应时间 |
服务器响应时间是指应用服务器处理请求所消耗的时间,即应用响应时间,等于请求到达应用服务器到应用代码执行完成并输出响应信息的时间。(需要通过Server探针自动注入方式嵌码,否则服务器响应时间为零) |
AJAX请求响应时间 |
所有Ajax请求时间在时间轴的投影合并的总耗时 |
unload |
卸载当前页面的耗时,计算公式:unloadEnd-unloadStart |
Redirect |
页面重定向操作所消耗的时间,计算公式:redirectEnd-redirectStart |
Cache |
取缓存数据的耗时,计算公式:domainLookupStart-fetchStart |
DNS |
通过域名解析服务(DNS),将指定的域名解析成IP地址的消耗时间。 |
TCP建连时间 |
浏览器和WEB服务器建立TCP/IP连接的消耗时间。当元素下载完成后,浏览器可能会根据服务器返回的结果保持此连接,而不是完全关闭此连接。当监测节点再次和相同的服务器建立连接时,会复用此连接,对应消耗时间可能为0。此指标即为TCP/IP连接三次握手的前二次握手的时间(从IE发送TCP包SYN到收到服务器返回的TCP包SYN ACK的时间),第三次握手时间(从IE发送TCP包ACK到服务器接收此TCP包的时间)不计算在内。 |
排队时间 |
排队时间指服务器端的请求阻塞时间,即请求从Web前端服务器(例如Apache, nginx或F5负载均衡设备)到达应用服务端的时间。 |
首包时间 |
从开始页面请求到浏览器开始接收到HTML代码的时间,不包括排队时间和服务器端的时间,计算公式:responseStart-connectEnd -排队时间-服务器响应时间 |
剩余包时间 |
从responseStart事件开始到responseEnd事件结束,计算公式:responseEnd-responseStart |
首次渲染时间 |
从导航到页面首次渲染消耗的时间,计算公式:firstPaintTime-navigationStart(又名:白屏时间,firstPaintTime) |
首次交互时间 |
从用户的第一个动作发生时间 – navigationStart,其中动作包括:点击,按键,滚动鼠标。 |
自定义加载时间(用户可感知时间) |
每个页面都可以设置一个用户自定义的加载时间性能指标。计算方式为路由切换时间至主动调用sdk ready方法时间差值 |
AJAX平响时间 |
平均每次AJAX请求的响应时间 |
AJAX传输数据量 |
单位KB,平均每次AJAX请求的数据传输量(上传+下载字节数) |
AJAX回调时间 |
平均每次AJAX请求的回调时间(回调时间是指当数据从服务器传到客户端之后,本地代码调用这些数据做相应的处理,可以理解为本地执行时间) |
客户端时间 |
从请求某资源到下载完过程中,没有出现网络传输的时间片段之和,比如DNS-TCP建连,之间的切换需要消耗CPU来调度,这就可能会产生很短的时间空隙 |
事件平均响应时间 |
操作请求完成时间。 |

Core Web Vitals(
https://web.dev/vitals/
)

metrics |
描述 |
含义 |
TTFB |
time to first byte |
从请求到数据返回第一个字节所消耗时间 |
TTI |
DOM树构建完毕,可绑定事件 |
|
DCL |
DOMContentLoaded |
HTML文档完全加载解析完成 |
L |
onLoad |
依赖资源全部加载完毕 |
FP |
first paint |
第一个像素点绘制完成时间 |
FCP |
首次绘制非空白节点时间 |
|
FMP |
first meaningful paint |
首次有意义绘制(需要自定义) |
LCP |
在视口中最大的页面元素加载时间 |
|
FID |
用户首次和页面交互到页面响应的时间 |
|
CLS |
度量在页面开始加载到其生命周期状态更改为隐藏之间发生的所有意外布局更改的累积分数 |
Web Vitals进行获取
interface Metric { // The name of the metric (in acronym form). name: 'CLS' | 'FCP' | 'FID' | 'LCP' | 'TTFB'; // The current value of the metric. value: number; // The delta between the current value and the last-reported value. // On the first report, `delta` and `value` will always be the same. delta: number; // A unique ID representing this particular metric that's specific to the // current page. This ID can be used by an analytics tool to dedupe // multiple values sent for the same metric, or to group multiple deltas // together and calculate a total. id: string; // Any performance entries used in the metric value calculation. // Note, entries will be added to the array as the value changes. entries: (PerformanceEntry | FirstInputPolyfillEntry | NavigationTimingPolyfillEntry)[]; }
- pageGuid,重新定义pageGuid生成规则,暂定为 router+orgId+timestamp+random ? 进行加密处理
- requestId,由网络库统一生成,生成方式参考BFE_logId生成规则,在请求header中携带,用于skywalking tid进行映射
- operateId,需要使用者明确【操作】起止时机,并手动调用SKD API。
1) 在使用方初始化【操作】时
,调用 SDK.initOpt('
操作名
'),该方法返回本次操作
operate
Name
,用于后续传递;
1) 在使用方明确【操作】开始时,调用SDK.startOpt('operateName'),SDK生成唯一operateId,存于localStorage.pageGuid..operateName.operateId(示例);
2) 为了保证上下文独立性,在该操作涉及到的请求options中配置{operateName: 'operateName'},网络库在发送请求时,主动获取operateId并携带,取值方式为:localStorage.pageGuid..operateName.operateId || ‘’;
3) 在使用方明确【操作】结束时,需调用SDK.endOperate(操作名);SDK将进行该次操作相关操作时间,页面渲染时长等统计,存储在localStorage.pageGuid.bucket中,通过上报策略时机进行统一上报。
2、上报
上报时机(备选)
页面加载和重新刷新页面切换路由
存储于
localStorage.pageGuid.bucket中;bucket中数量超过阈值,触发上报;页面卸载unload,触发上报;
上报方式
如果页面销毁,那么对应的ajax方法并不一定能成功发送。
(1)、sendBeacon方法
function sendBeacon(url,data){//判断支不支持navigator.sendBeacon let headers ={
type:'application/x-www-form-urlencoded'};
let blob= newBlob([JSON.stringify(data)], headers);
navigator.sendBeacon(url,blob);
}
(2)动态创建img标签的形式
function imgReport(url, data) {if (!url || !data) {return;
}
let image= document.createElement('img');
let items=[];
items=JSON.Parse(data);
let name= 'img_' + (+newDate());
image.onload= image.onerror =function () {
};
let newUrl= url + (url.indexOf('?') < 0 ? '?' : '&') + items.join('&');
image.src=newUrl;
}
(3)同步ajax post请求
function xmlLoadData(url,data) { var client = new XMLHttpRequest(); client.open("POST", url,false); client.setRequestHeader("Content-Type", "application/json; charset=utf-8"); client.send(JSON.stringify(data)); }
(4)综合解决方案
function dealWithUrl(url,appId){
let times=performanceInfo(appId);
let items=decoupling(times);
let urlLength= (url + (url.indexOf('?') < 0 ? '?' : '&') + items.join('&')).length;if(urlLength<2083){
imgReport(url,times);
}else if(navigator.sendBeacon){
sendBeacon(url,times);
}else{
xmlLoadData(url,times);
}
}
3、方案设计(Hybrid H5)
3.1总体架构
