指标

我们可转化为三个方面来看:响应速度、页面稳定性、外部服务调用

响应速度 页面初始访问速度 + 交互响应速度
页面稳定性 页面出错率
外部服务调用 网络请求访问速度

页面访问速度:白屏、首屏时间、可交互时间

image.png

FP, FCP:
image.png

可交互的时间点TTi
image.png



白屏时间: domLoading - fetchStart
粗略首屏时间: loadEventEnd - fetchStart 或者 domInteractive - fetchStart
DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart


JS 总加载耗时:
(构建一个内敛 js需要执行10s的js脚本,如何写?

  1. const p = window.performance.getEntries();
  2. let cssR = p.filter(ele => ele.initiatorType === "script");
  3. Math.max(...cssR.map((ele) => ele.responseEnd)) - Math.min(...cssR.map((ele) => ele.startTime));

CSS 总加载耗时:
(疑问:如何构建一个需要加载10s的css插入文档,用于测试页面加载速度?

  1. const p = window.performance.getEntries();
  2. let cssR = p.filter(ele => ele.initiatorType === "css");
  3. Math.max(...cssR.map((ele) => ele.responseEnd)) - Math.min(...cssR.map((ele) => ele.startTime));



长任务
浏览器是单线程的,如果长任务过多,那必然会影响着用户响应时长。好的应用需要最大化空闲时间,以保证能最快响应用户的输入。
image.png

image.png

FPS页面帧率

FPS 是来自视频或者游戏里的概念,即是每秒的帧数,代表视频或者游戏的流畅度,俗话说,就是“不卡”。

网页的 FPS 是只浏览器在渲染这些变化时的帧率。帧率越高,用户感觉网页越流畅,反之则会感觉卡顿。

最优的帧率是 60,即16.5ms 左右渲染一次

image.png

绿色的直方图即代表在页面重新绘制时的帧率,Frames 为每一帧渲染所花的时间。

页面稳定性:页面出错情况

资源加载错误JS 执行报错

外部服务调用

CGI 耗时CGI 成功率CDN 资源耗时

Resource Timing API
performance.getEntriesByType(“resource”);
image.png

  1. // 某类资源的加载时间,可测量图片、js、css、XHR
  2. resourceListEntries.forEach(resource => {
  3. if (resource.initiatorType == 'img') {
  4. console.info(`Time taken to load ${resource.name}: `, resource.responseEnd - resource.startTime);
  5. }
  6. });

数据和 chrome 调式工具里 network 的瀑布图数据一样

performance

performance.timing: 页面加载生命周期

image.png
image.png

  • PerformanceTiming.navigationStart:当前浏览器窗口的前一个网页关闭,发生 unload 事件时的时间戳
  • PerformanceTiming.domLoading:返回当前网页 DOM 结构开始解析时(即Document.readyState属性变为“loading”、相应的readystatechange事件触发时)的时间戳
  • PerformanceTiming.domInteractive:返回当前网页 DOM 结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的时间戳
  • PerformanceTiming.domComplete:返回当前文档解析完成(即Document.readyState变为”complete”且相对应的readystatechange)被触发时的时间戳
  • PerformanceTiming.loadEventStart:返回该文档下,load 事件被发送时的时间戳
  • PerformanceTiming.loadEventEnd:返回当 load 事件结束,即加载事件完成时的时间戳

DOMContentLoaded:HTML 文档被完全加载和解析完成,而无需等待样式表、图像和子框架的完全加载
(前端框架里使用这个无意义,因为框架是js生成的dom,在框架本身提供的生命周期函数收集即可)

运行时性能指标

Performance.getMetrics()

  • Timestamp: 采取度量样本的时间戳
  • Documents: 页面中的文档数
  • Frames: 页面中的帧数
  • JSEventListeners: 页面中的事件数
  • Nodes: 页面中的 DOM 节点数
  • LayoutCount: 全部或部分页面布局的总数
  • RecalcStyleCount: 页面样式重新计算的总数
  • LayoutDuration: 所有页面布局的合并持续时间
  • RecalcStyleDuration: 所有页面样式重新计算的总持续时间
  • ScriptDuration: JavaScript 执行的持续时间
  • TaskDuration: 浏览器执行的所有任务的合并持续时间
  • JSHeapUsedSize: 使用的 JavaScript 栈大小
  • JSHeapTotalSize: JavaScript 栈总大小

打点计算某函数执行耗时

  1. async function run() {
  2. performance.mark("startTask1");
  3. await doTask1(); // Some developer code
  4. performance.mark("endTask1");
  5. performance.mark("startTask2");
  6. await doTask2(); // Some developer code
  7. performance.mark("endTask2");
  8. // Log them out
  9. const entries = performance.getEntriesByType("mark");
  10. for (const entry of entries) {
  11. console.table(entry.toJSON());
  12. }
  13. }

MutationObserver

使用MutationObserver接口监听页面 DOM 树变化的能力,结合performance获取到具体的时间:

  1. // 注册监听函数
  2. const observer = new MutationObserver((mutations) => {
  3. console.log(`时间:${performance.now()},DOM树发生了变化!有以下变化类型:`);
  4. for (let i = 0; i < mutations.length; i++) {
  5. console.log(mutations[0].type);
  6. }
  7. });
  8. // 开始监听document的节点变化
  9. observer.observe(document, {
  10. childList: true,
  11. subtree: true,
  12. });

performance.getEntries()

通过这个方法可以获取到所有的 performance 实体对象,通过 getEntriesByName 和 getEntriesByType 方法可对所有的 performance 实体对象 进行过滤,返回特定类型的实体。
mark 方法 和 measure 方法的结合可打点计时,获取某个函数执行耗时

performance.mark()
performance.measure()

chrome的performance面板

image.png

  • 查看 FPS 图表:当在 FPS 上方看到红色条形时,表示帧速率下降得太低,以至于可能损害用户体验。通常,绿色条越高,FPS 越高
  • 查看 CPU 图表:CPU 图表在 FPS 图表下方。CPU 图表的颜色对应于性能板的底部的 Summary 选项卡
  • 查看 火焰图:火焰图直观地表示出了内部的 CPU 分析,横轴是时间,纵轴是调用指针,调用栈最顶端的函数在最下方。启用 JS 分析器后,火焰图会显示调用的每个 JavaScript 函数,可用于分析具体函数
  • 查看 Buttom-up:此视图可以看到某些函数对性能影响最大,并能够检查这些函数的调用路径

计算首屏时间

首屏时间是一项重要指标,但是又很难从 performance 中拿到,来看下首屏时间计算主要有哪些方式?

1)用户自定义打点—最准确的方式(只有用户自己最清楚,什么样的时间才算是首屏加载完成)
2)lighthouse 中使用的是 chrome 渲染过程中记录的 trace event
3)可利用Chrome DevTools Protocol拿到页面布局节点数目。思想是:获取到当页面具有最大布局变化的时间点4)aegis 的方法:利用 MutationObserver 接口,监听 document 对象的节点变化。检查这些变化的节点是否显示在首屏中,若这些节点在首屏中,那当前的时间点即为首屏渲染时间。但是还有首屏内图片的加载时间需要考虑,遍历 performance.getEntries() 拿到的所有图片实体对象,根据图片的初始加载时间和加载完成时间去更新首屏渲染时间。
5)利用 MutationObserver 接口提供了监视对 DOM 树所做更改的能力,是 DOM3 Events 规范的一部分。方法:在首屏内容模块插入一个 div,利用 Mutation Observer API 监听该 div 的 dom 事件,判断该 div 的高度是否大于 0 或者大于指定值,如果大于了,就表示主要内容已经渲染出来,可计算首屏时间。
6)某个专利:在 loading 状态下循环判断当前页面高度是否大于屏幕高度,若大于,则获取到当前页面的屏幕图像,通过逐像素对比来判断页面渲染是否已满屏https://patentimages.storage.googleapis.com/bd/83/3d/f65775c31c7120/CN103324521A.pdf
web性能监控 - 图11

计算帧率来判断页面的卡顿情况

计算帧率

如果页面无法通过performance得到fps,如何自己计算帧率?
1、通过raf计算相对帧率:
原理:
通过 requestAnimationFrame API 来定时执行一些 JS 代码,如果浏览器卡顿,无法很好地保证渲染的频率,1s 中 frame 无法达到 60 帧,即可间接地反映浏览器的渲染帧率

  1. var lastTime = performance.now();
  2. var frame = 0;
  3. var lastFameTime = performance.now();
  4. var loop = function(time) {
  5. var now = performance.now();
  6. var fs = (now - lastFameTime);
  7. lastFameTime = now;
  8. var fps = Math.round(1000/fs);
  9. frame++;
  10. if (now > 1000 + lastTime) {
  11. var fps = Math.round( ( frame * 1000 ) / ( now - lastTime ) );
  12. frame = 0;
  13. lastTime = now;
  14. };
  15. window.requestAnimationFrame(loop);
  16. }

代码摘自淘宝前端团队的《无线性能优化:FPS 测试》

requestAnimationFrame()方法的调用频率就是我们需要的fps
1s内调用xx次就是1帧,不卡顿正常的情况下是调用60次

2、使用`window.performance.now()获取当前的时间,根据下一次绘制时间计算时间间隔,计算fps

  1. <canvas id="_fps_canvas"></canvas>
  2. <script>
  3. let canvas = document.getElementById('_fps_canvas');
  4. let ctx = canvas && canvas.getContext('2d');
  5. if(!ctx) {
  6. console.info("浏览器不支持canvas")
  7. } else {
  8. let lastTime = 0;
  9. let fpsInterVal = 30; // fps监听间隔次数
  10. let fpsCount = 0; // fps监听间隔计数
  11. let fps = 0; // fps值
  12. let getFps = function() {
  13. fpsCount++;
  14. let nowTime = performance.now();
  15. if(fpsCount >= fpsInterVal) {
  16. fps = Math.round(1000 * fpsCount / ( nowTime - lastTime ));
  17. lastTime = nowTime;
  18. fpsCount = 0;
  19. }
  20. return fps;
  21. }
  22. let clearCanvas = function() {
  23. ctx.clearRect(0, 0, canvas.width, canvas.height);
  24. }
  25. let startDraw = function(time) {
  26. clearCanvas();
  27. ctx.font = "22px serif";
  28. ctx.fillStyle = "#558abb";
  29. ctx.fillText(getFps() + ' fps', 10, 20);
  30. window.requestAnimationFrame(startDraw)
  31. }
  32. startDraw();
  33. }
  34. </script>

通过 FPS 确定网页卡顿情况

按照我们对卡顿的观察,连续出现3个低于20的 FPS即可认为网页存在卡顿。

  1. function isBlocking(fpsList, below=20, last=3) {
  2. var count = 0
  3. for(var i = 0; i < fpsList.length; i++) {
  4. if (fpsList[i] && fpsList[i] < below) {
  5. count++;
  6. } else {
  7. count = 0
  8. }
  9. if (count >= last) {
  10. return true
  11. }
  12. }
  13. return false
  14. }

数据上报

采集数据:将 performance navagation timing 中的所有点都上报,其余的上报内容可参考 performance 分析一节中截取部分上报。例如:白屏时间,JS 和 CSS 总数,以及加载总时长。
其余可参考的上报:是否有缓存?是否启用 gzip 压缩、页面加载方式。
在收集好性能数据后,即可将数据上报。
那选择什么时机上报?
google 开发者推荐的上报方式:
image.png

1)js error监听 window.onerror 事件
2)promise reject 的异常监听 unhandledrejection 事件

  1. window.addEventListener("unhandledrejection", function (event) {
  2. console.warn("WARNING: Unhandled promise rejection. Shame on you! Reason: "
  3. + event.reason);
  4. });

3)资源加载失败window.addEventListener(‘error’)
4)网络请求失败重写 window.XMLHttpRequest 和 window.fetch 捕获请求错误
5)iframe 异常window.frames[0].onerror
6)window.console.error

参考资料

web如何进行性能监控
前端监控体系搭建
前端性能分析工具扫盲
寸志-如何监控网页卡顿