在[上一节](https://www.cnblogs.com/strick/p/17072053.html)中曾提到过两种性能监控:SYN 和 RUM,那么对应的也有两种分析:数据分析和实验室分析。
  数据分析会通过采集上来的性能信息来剖析和定位可能存在的各种问题。
  实验室分析会通过某个线上或本地的测试工具对页面进行单点测试,得出性能分析报告。
  本文会对前者介绍一些分析实践,后者会介绍一些比较有名的性能测试工具。
  数据分析的前端代码已上传至[shin-admin](https://github.com/pwstrick/shin-admin),后端代码上传至[shin-server](https://github.com/pwstrick/shin-server)。
## 一、数据分析
  在将数据采集到后就需要立刻存储,并且按百分位数计算后,需要定期计算和清理。
  各类图表的辅助可以更好的定位到发生的性能问题。
**1)存储**
  在将性能数据采集到后,就需要将它们存储到数据库中,例如 MySQL、MongoDB 等。
  2023-04-13 如果将数据存储在 MongoDB 中,那么就可以将 JSON 中的属性作为条件来查询,例如查询 measure 对象中的 TTFB 大于 10 的记录。
  而 MySQL 中的 JSON 都是字符串序列化后再存储到数据库中的,所以如果要对某个属性做查询,会比较困难。
  为了避免拖垮服务器,在服务端接收时会通过队列来异步新增。
  以我当前公司的实践为例,将性能数据存储到 web\_performance 表中,表结构如下。
~~~
CREATE TABLE `web_performance` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`load` int(11) NOT NULL DEFAULT '0' COMMENT '页面加载总时间',
`ready` int(11) NOT NULL DEFAULT '0' COMMENT '用户可操作时间',
`paint` int(11) NOT NULL DEFAULT '0' COMMENT '白屏时间',
`screen` int(11) NOT NULL DEFAULT '0' COMMENT '首屏时间',
`network` int(11) NOT NULL DEFAULT '0' COMMENT '网络时间,TTFB + responseDocumentTime',
`measure` varchar(1000) COLLATE utf8mb4_bin NOT NULL COMMENT '其它测量参数,用JSON格式保存',
`measure_bottleneck` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '瓶颈指标',
`ctime` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`day` int(11) NOT NULL COMMENT '格式化的天(冗余字段),用于排序,20210322',
`hour` tinyint(2) NOT NULL COMMENT '格式化的小时(冗余字段),用于分组,11',
`minute` tinyint(2) DEFAULT NULL COMMENT '格式化的分钟(冗余字段),用于分组,20',
`identity` varchar(30) COLLATE utf8mb4_bin NOT NULL COMMENT '身份',
`project` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '项目关键字,关联 web_performance_project 表中的key',
`ua` varchar(600) COLLATE utf8mb4_bin NOT NULL COMMENT '代理信息',
`referer` varchar(300) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '来源地址',
`referer_path` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '来源地址中的路径',
`timing` text COLLATE utf8mb4_bin COMMENT '浏览器读取到的性能参数,用于排查',
`resource` text COLLATE utf8mb4_bin COMMENT '静态资源信息',
`resource_count` int(11) DEFAULT '0' COMMENT '静态资源数量',
`os_name` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '操作系统名称',
`os_version` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '操作系统版本',
`app_version` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '客户端版本',
`ip` varchar(200) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '客户端IP',
`country` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '国家',
`province` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '省份',
`city` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '城市',
`isp` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '网络运营商',
PRIMARY KEY (`id`),
KEY `idx_project_day` (`project`,`day`),
KEY `idx_project_day_hour` (`project`,`day`,`hour`),
KEY `idx_ctime` (`ctime`),
KEY `idx_identity_referer_project` (`identity`, `referer_path`, `project`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='性能监控'
~~~
  referer\_path 字段,用于分析指定页面的性能。
  2023-05-24 将表中的 referer 字段长度从 200 增加到 300。
  2023-05-24 在表中新增 os_name、os_version、app_version 和 ip 四个字段,用于做性能日志的分析。
  2023-06-04 在表中新增 network 网络时间字段,其值是 TTFB 和 responseDocumentTime 相加得到的和。
  2023-06-07 在表中新增 country、province、city 和 isp 四个字段,存储 IP 解析后的信息,包括国家、省份、城市和网络运营商。
  IP 解析可以选择付费服务,得到的结果比较准确,并且还能保持更新。或者选择开源的[离线 IP 库](https://github.com/lionsoul2014/ip2region),虽然精度不高,但是免费。
  2023-09-26 在表中新增 measure_bottleneck 字段,存储瓶颈指标,可选值包括 TTFB、responseDocumentTime、initDomTreeTime、parseDomTime、loadEventTime。
  2023-12-15 在表中新增 resource_count 字段,存储静态资源的数量。
  表中的 project 字段会关联 web\_performance\_project 表(结构如下所示)中的 key。
~~~
CREATE TABLE `web_performance_project` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`key` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '唯一值',
`name` varchar(45) COLLATE utf8mb4_bin NOT NULL COMMENT '项目名称',
`ctime` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1:正常 0:删除',
PRIMARY KEY (`id`),
UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='性能监控项目';
~~~
  性能项目就是要监控的页面,与之前不同,性能的监控粒度会更细,因此需要有个后台专门管理这类数据。
  key 值是通过名称得到 16 位 MD5 字符串,需要引入 Node.js 的 cryto 库,如下所示。
~~~
const crypto = require('crypto');
const key = crypto.createHash('md5').update(name).digest('hex').substring(0, 16);
~~~
  可以对长期维护的网页创建单独的性能项目,而对于那些临时活动可以共用一个项目。
**2)百分位数**
  均值会受极值的影响,从而让它不够准确,无法真实的反映出用户的性能情况。
  故而选择了百分位数来解决极值问题,例如前 95% 用户的首屏时间在 2s 内,
  这种写法也叫 TP95,表示 95 分位数,TP 是 Top Percentile 的缩写。
  95 分位数是比较高的统计指标,意味着大多数的用户都能享受到更好的性能体验。
  为了能看到变化趋势,可以采用图表的方式,例如折线图,如下所示,横坐标可按天或小时。
:-: ![](https://img.kancloud.cn/44/42/444255b86a98cef43fed79d353c850c4_1872x1270.png =800x)
  2023-06-15 增加分位数下拉选择框,可选择 50、75、95 三项。
  75 分位数是大多数用户的表现,95 分位数是比较极端的场景的用户表现,当前默认采用 95 分位数。
:-: ![](https://img.kancloud.cn/2a/51/2a51cd0b8905a956491e8584fe1f8b32_2204x436.png =800x)
  2023-07-06 增加分位数箱型图,这是一种用作显示一组数据分散情况资料的统计图。
  在此图中,将 95、75、50、25 以及最小值全部绘制在一个箱子中,清晰地看出这批数据的中位数、异常值、分布区间等信息。
:-: ![](https://img.kancloud.cn/8e/02/8e025eb74459ca624c7d32278446d659_2986x1156.png =800x)
**3)定时任务**
  每天可以选一个时间(例如凌晨三点),来统计昨天的日志信息。
  例如将计算得到的统计信息以 JSON 格式(如下所示)存储到数据库表的一个字段中。
  这个字段可以是 TEXT 类型或更大的 MEDIUMTEXT,一天只插入一条记录。
~~~
{
hour: {
x: [11, 14],
load: ["158", "162"],
ready: ["157", "162"],
paint: ["158", "162"],
screen: ["157", "162"]
},
minute: {
11: {
x: [11, 18, 30],
load: ["157", "159", "160"],
ready: ["156", "159", "160"],
paint: ["157", "159", "160"],
screen: ["156", "159", "160"]
},
14: {
x: [9, 16, 17, 18],
load: ["161", "163", "164", "165"],
ready: ["161", "163", "164", "165"],
paint: ["161", "163", "164", "165"],
screen: ["161", "163", "164", "165"]
}
}
}
~~~
  还可以选一个时间来做数据清理,因为没有必要一直将这么多的数据保留着。
**4)资源瀑布图**
  通过资源瀑布图可以查看当时的资源加载情况。
  在上报性能参数时,将静态资源的耗时通过 getEntriesByType() 方法得到(如下所示),然后一起打包给服务器。
~~~
// 静态资源列表
const resources = performance.getEntriesByType("resource");
const newResources: TypeSendResource[] = [];
resources && resources.forEach((value: PerformanceResourceTiming): void => {
const { name, initiatorType, startTime, duration,transferSize } = value;
// 过滤 fetch 请求
if(initiatorType === 'fetch') return;
// 只存储 1 分钟内的资源
if(startTime > 60000) return;
newResources.push({
name: name,
duration: rounded(duration),
startTime: rounded(startTime),
transferSize: transferSize // 资源的总大小,包括HTTP首部
});
});
obj.resource = newResources;
~~~
  由于我本地业务请求使用的是 XMLHTTPRequest,因此在代码中会过滤掉 fetch 请求,只在上报监控数据时采用了 fetch() 函数。
  这可以根据实际情况来处理。在搜集资源时,1 分钟以外的都会舍弃,并且只记录了资源名称、耗时和开始时间。
  2023-12-04 getEntriesByType("resource") 读取的是一个[PerformanceResourceTiming](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming)实例,它有许多个属性,其中:
* transferSize:资源的总大小,包括 HTTP 首部。
* encodedBodySize:资源的压缩大小,不包括 HTTP 首部。
* decodedBodySize:资源的原始大小,不包括 HTTP 首部。
  在上述的 forEach 遍历的最后增加如下代码,原先计划存储白屏和首屏两个时刻的资源大小。
  但是发现白屏之前的资源大小要么是 0,要么与首屏时刻的比较接近。
~~~
// 过滤 undefined 和 0 的字段
if(!transferSize)
return;
// 计算白屏之前的资源总大小
if(startTime <= obj.firstPaint) {
transferPaintSize += transferSize;
}
// 计算首屏之前的资源总大小
if(startTime <= obj.firstScreen) {
transferScreenSize += transferSize;
}
~~~
  分析发现,白屏在 1 秒以内,大小为 0 的情况会比较多,可以理解此时获得了 HTML 文件,但还没开始请求结构内的资源。
  而在 1 秒以上时,大小接近的情况会比较多。既然此数据并不稳定,那么就只记录首屏之前的资源总大小(transferScreenSize)。
  在后续的散点图中,就能展示首屏和资源之间的相关性。
  最终的资源瀑布图效果如下所示,包含一个横向的柱状图,并且在图中会标注白屏、首屏、load 和 DOMContentLoaded 的时间点。
:-: ![](https://img.kancloud.cn/ca/ce/caceb72013f65604ca7c5239866c0e81_2030x1006.png =800x)
  这样能对资源的加载做更直观的比较,便于定位性能问题。
  2023-07-11 发现 DOMContentLoaded 和 load 的时间异常巨大(如下图所示),瀑布图已经消失,查看数据库了解到性能参数读取自第一版的规范。
:-: ![](https://img.kancloud.cn/dd/1e/dd1efc080718b03ff0ad8b9fa76b09c0_2020x1010.png =800x)
  所以需要对参数进行额外的计算,即与 fetchStart 相减。
~~~
const caculateMeasure = {
paint: record.paint,
screen: record.screen,
ready: originalTiming.domContentLoadedEventStart,
load: originalTiming.loadEventStart
};
// 需要判断性能参数是否来自于第一版
if (!originalTiming.entryType) {
caculateMeasure.ready = originalTiming.domContentLoadedEventStart - originalTiming.fetchStart;
caculateMeasure.load = originalTiming.loadEventStart - originalTiming.fetchStart;
}
~~~
**5)堆叠柱状图**
  先将所有的性能记录统计出来,然后分别统计白屏和首屏 1 秒内的数量、1-2 秒内、2-3 秒内、3-4 秒内、4+秒的数量,白屏的 SQL 如下所示。
~~~
SELECT COUNT(*) FROM `web_performance` WHERE `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
SELECT COUNT(*) FROM `web_performance` WHERE `paint` <= 1000 and `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
SELECT COUNT(*) FROM `web_performance` WHERE `paint` > 1000 and `paint` <= 2000 and `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
SELECT COUNT(*) FROM `web_performance` WHERE `paint` > 2000 and `paint` <= 3000 and `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
SELECT COUNT(*) FROM `web_performance` WHERE `paint` > 3000 and `paint` <= 4000 and `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
SELECT COUNT(*) FROM `web_performance` WHERE `paint` > 4000 `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
~~~
  算出后,分母为总数,分子为上述五个值,组成一张堆叠柱状图,类似于下面这样,每种颜色代码一个占比。
:-: ![](https://img.kancloud.cn/c1/a8/c1a8c221a50165ea7c4d050c37a3d67f_800x531.png =600x)
  这样就能直观的看到优化后的性能变化了,可更快的反馈优化结果。
  2024-03-19 将白屏和首屏的堆叠柱状图修改成堆叠面积图,为了能更好的查看变化趋势。
:-: ![](https://img.kancloud.cn/f0/90/f0902ad8a4a7613dae5f9e4cc68deae8_1682x796.png =600x)
**6)阶段时序图**
  在将统计的参数全部计算出来后,为了能更直观的发现性能瓶颈,设计了一张阶段时序图。
  描绘出 TTFB、responseDocumentTime、initDomTreeTime、parseDomTime 和 loadEventTime 所占用的时间,如下所示。
  橙色竖线表示白屏时间,黑色竖线表示首屏时间。移动到 id 或来源地址,就会提示各类参数。
:-: ![](https://img.kancloud.cn/d9/47/d947b7f1216baf7e50101fff03017710_2978x1082.png =800x)
  2023-05-24 对阶段时序图进行了一次比较大的优化。
  在阶段时序图中,增加几个过滤条件,并且点击 id,可以从屏幕右侧边缘滑出浮层面板(采用[抽屉式的交互](https://4x.ant.design/components/drawer-cn/))。
  2023-09-25 增加瓶颈指标,这样就能一眼了解到最费时的阶段,而性能测试是一个集成了 Lighthouse 的系统,点击就能生成一份性能报告。
  2023-09-26 将各个阶段的数量,在查询后也一并列出,按从大到小排列。每条记录再增加区域、identity、操作系统和手机型号。
:-: ![](https://img.kancloud.cn/64/a5/64a526f84b40aa8f2641dc537ac35163_2994x1082.png =800x)
  在弹层中,基本信息包括 IP、客户端版本、解析的 UA 信息等。
:-: ![](https://img.kancloud.cn/11/61/11611ca345c0f719a5a653362b6149bb_3240x412.png =800x)
  机型提供了跳转地址,点击后就能自动到搜索引擎页面。
:-: ![](https://img.kancloud.cn/bd/9f/bd9f0e9c6b5cbd1ea5fa95a564815f0f_1410x568.png =600x)
  性能数据包括 Web Core Vitals 中的 LCP、FP 等,还有经过计算的性能数据,以及浏览器提供的原始性能参数。
:-: ![](https://img.kancloud.cn/ae/18/ae1834d2ca3be87134619760a0526a02_3244x1468.png =800x)
  2024-03-21 为部分指标增加颜色标识,更容易识别当前数据是良好(绿色)、待提升(橙色)还是糟糕(红色)的。
:-: ![](https://img.kancloud.cn/36/6e/366ee851dc6f29ffa3135c1baeb8e1d3_3220x254.png =800x)
  2023-09-25 增加性能雷达图,将那 5 个阶段指标通过雷达图描绘出,能更容易观察性能差异。
:-: ![](https://img.kancloud.cn/e5/f2/e5f2978a0c6b4c8f6a751332ae16dd7e_962x576.png =400x)
  资源瀑布图就是之前那功能,只是搬到了弹层中,就能通过当前日志自动绘制出来,而不必再手动搜索了。
  当日性能覆盖会统计处于相同性能参数范围的白屏和首屏数据,例如当前是1-2秒内,则只统计此区间段内的数据。
:-: ![](https://img.kancloud.cn/b5/c7/b5c7060a21cd8496457a28fbbb6c499b_2818x974.png =800x)
  其中操作系统会加上版本号,例如 Android 13、iOS 15.3.1。客户端会附带 APP 的版本号。区间内操作系统的数量,也会有所统计。
:-: ![](https://img.kancloud.cn/6d/bf/6dbf573093c07ff43e464f80a3d54e65_3238x388.png =800x)
  2023-06-26 在正式上线后发现,操作系统细分的小版本众多,用饼图呈现会比较杂乱。
:-: ![](https://img.kancloud.cn/e1/d4/e1d4bcf6c150ed84d5cdcebdc848ee9c_1584x862.png =600x)
  于是就改成了条形图,并且做了排序,只展示前 20 条数据,呈现效果非常清晰,重点信息也能突出。
:-: ![](https://img.kancloud.cn/9b/dc/9bdc7b3f879ed9521f57520b699a07d9_1622x866.png =600x)
  最后会展示行为轨迹,性能日志中有个 identity 字段,可以关联前端监控日志,这样就能了解当前性能是否真的在影响用户的使用体验。
:-: ![](https://img.kancloud.cn/62/51/625173105d1cd0cd7154e68634438bf5_2766x1448.png =800x)
  2023-07-04 增加性能榜单,将白屏时间超过 2 秒的数据分成 3 组,每组显示页面路径和数量,倒序排列。
:-: ![](https://img.kancloud.cn/40/ef/40ef3e6e07d577ef23d7c21ba7f5b2f2_3098x1022.png =800x)
  从榜单中就可知性能差的页面有哪些,需要重点关注,当然,如果页面访问量大,那差页面的占比自然也会高点。
**7)散点图**
  2023-12-01 散点图会将所有的数据以点的形式展现在直角坐标系上,用于展示两个变量之间的相关性,而横轴和纵轴不一定从 0 开始。
  如果两个变量的变化方向相同,则称之为正相关,同时变大或同时变小。若变化相反,则是负相关;没有明显的关系,则是不相关。
:-: ![](https://img.kancloud.cn/8a/59/8a5978c31bdc0163c03d9e9454c5bffa_321x420.png)
  散点图适合那些变量之间存在密切关系,但是这些关系不能用数学公式精确表达的场景。
  但是在分析过程中需要注意,这两个变量之间的相关性并不等同于确定的因果关系。
  在性能监控中,将关键指标(白屏和首屏)和性能参数(TTFB、responseDocumentTime等)之间的关系绘制成了散点图。
  在查询条件中,还可以选择性能项目和分位数,如下图所示。
:-: ![](https://img.kancloud.cn/2d/dd/2dddb2f1ceb4d922ce81c34dd1b05df7_2992x1092.png =800x)
**8)气泡图**
  2023-12-15 气泡图通常用于展示和比较数据之间的关系和分布,通过比较气泡位置和大小来分析数据维度之间的相关性。
  气泡图是散点图的变形,由三个变量组成,例如用气泡图展示历史上世界各国人均收入、人口数量和寿命的关系。
  在性能监控中,增加页面的资源数量、首屏时间和访问量之间的关系,SQL 语句如下所示。
~~~
SELECT `referer_path`, COUNT(*) as size, ROUND(AVG(`resource_count`)) as x, ROUND(AVG(`screen`)) as y
FROM `web_performance` WHERE `ctime` >= '2023-12-15 00:00' and `ctime` < '2023-12-26 00:00'
GROUP BY `project`;
~~~
  其中资源数量和首屏时间都取平均数。在查询条件中,可以选择时间范围,如下图所示。
:-: ![](https://img.kancloud.cn/2b/1c/2b1c38d55f5033da9ee7dc0f517a8adc_1874x1116.png =800x)
## 二、实验室分析
  成熟的性能优化工具不仅能给出网络、渲染等信息,还能给出各种经过实践的优化建议,让我们的优化工作事半功倍。
**1)Chrome DevTools**
  Chrome 的[DevTools](https://developer.chrome.com/docs/devtools/overview/)是一款内置的开发者工具,可用于调试页面、查看网络请求、打印日志等常规功能。
  还提供了 Performance 面板,专门用于性能分析,可查看性能参数的时间点、各阶段的耗时、内存变化等。
  限于篇幅原因,本文只会重点讲解[Network](https://developer.chrome.com/docs/devtools/network/reference/)和[Performance](https://developer.chrome.com/docs/devtools/performance/reference/)两块面板。
  在 Network 中会呈现页面中所有的网络请求(可指定类型),并且会给出状态码、协议、请求瀑布图等信息。
:-: ![](https://img.kancloud.cn/35/18/35188a0b40d90f064f9dae2ad2193002_2240x936.png =800x)
  蓝线是 DOMContentLoaded(DCL) 事件触发的时间点,红线是 Load 事件触发的时间点。
  No throttling 用于模拟网速,模拟 4G、3G 或 Offline 离线等网络,在实际开发中很常用。
  Performance 需要点击录制按钮后,才会开始分析,如下图所示,内容还是比较多的。
:-: ![](https://img.kancloud.cn/0b/56/0b5637ca82fa523681cf287f07032b10_2244x1642.png =800x)
  在 Frames 中可以查看各个阶段的页面截图,Timings 中可以查看到 FP、LCP 等性能参数的时间点。
  Main 指的是当前页面,火焰图中描绘了 JavaScript 的性能。
  例如 Parse HTML、Evaluate Script(加载 JavaScript)、Compile Script(执行 JavaScript)等。
  在 Summary 的环形图中,可以看到火焰图中各种颜色对应的操作,例如:
* 蓝色 Loading 表示加载中,对 HTML、CSS 等资源进行解析工作。
* 黄色 Scripting 表示执行脚本,例如执行函数、触发事件等。
* 紫色 Rendering 表示渲染,包括 HTML 和 CSS 的变化,例如重绘或重排。
* 绿色 Painting 表示绘制,将合成的图层绘制到屏幕中。
  Performance 中的 Network 更注重时序和优先级,可在此查看资源加载是否符合预期。
:-: ![](https://img.kancloud.cn/20/ea/20eaf3e4f0e1c54661dd1b4e46bcccbb_2206x284.png =800x)
  内存视图是一个用不同颜色标注的折线图(如下所示),包括 JavaScript 堆、DOM 节点数量、事件监听器数量等信息。
:-: ![](https://img.kancloud.cn/87/9f/879fe1778d14c54186d35e66df2649ed_2188x370.png =800x)
  此处只是蜻蜓点水般介绍了下 Performance 的功能,详细内容还可以去参考官方[英语文档](https://developer.chrome.com/docs/devtools/performance/reference/)。
**2)WebPageTest**
  WebPageTest 是一款线上[性能分析工具](https://www.webpagetest.org/),通过布置一些特定的场景进行测试,例如不同的网速、浏览器、位置等。
  测试完成后,会给出一份性能报告,包括优化等级、性能参数、请求瀑布图、网页幻灯片快照、视频等。
  WebPageTest 的原理是将配置参数发送到后台,然后通过浏览器相关的代理程序,启动 Chrome、Firefox 或 IE。
  执行完毕后将数据回传给后台,后台再将数据保存起来,最后通过各种形式(统计图、表格等),将分析过的数据呈现给用户。
  如果是新手,官方还提供了一份快速[入门指南](https://docs.webpagetest.org/getting-started/)作为参考。多年前曾对 WebPageTest 做过[分析](https://www.cnblogs.com/strick/category/980651.html),有些内容仍然具有参考价值。
  在选择完浏览器和地区后(如下图所示),点击 Start Test 就开始测试了。
:-: ![](https://img.kancloud.cn/7c/cd/7ccddbcaabab5ed6a03feccb5cd5070e_1426x721.png =800x)
  性能报告的第一部分是优化建议和各种指标,包括 LCP、FCP、CLS 等,如下所示。
:-: ![](https://img.kancloud.cn/46/f9/46f9f0246ae66f812afffe2e3bfc360a_1429x746.png =800x)
  Speed Index 表示速度指数,衡量页面内容填充的速度(越低越好),适合页面优化前后的对比。
  在 Requests Details 中,呈现了请求信息,视图包括资源瀑布图、连接时序图、请求耗时表、各条请求的头信息。
:-: ![](https://img.kancloud.cn/c2/3d/c23d5cd78ab056906b763df407d3fa94_1298x453.png =800x)
:-: ![](https://img.kancloud.cn/d5/1a/d51a79263d65a7af1ccf31d418e0fbdb_1219x492.png =800x)
:-: ![](https://img.kancloud.cn/ae/00/ae00081b3859afb13ac83ea38bf5d6a4_1407x458.png =800x)
  在 WebPageTest 的幻灯片视图(Filmstrip View)中,在滑动滚动条时,下面会有根红线对应这个时刻的资源载入情况。
:-: ![](https://img.kancloud.cn/3a/18/3a189504c4b96140f29350d4eb9ad98e_1059x558.gif =800x)
**3)Lighthouse**
  Lighthouse 会对测试的网站进行打分,包括性能、可访问性、最佳实践、SEO 和 PWA 五个部分,并且会提供这五个部分的优化建议。
  测量的指标有 6 个,FCP、SI、LCP、CLS、TTI 和 TBT,如下所示。
:-: ![](https://img.kancloud.cn/fa/1c/fa1c2d6e6d384394dc22ce661c1ed9be_1608x662.png =800x)
  Lighthouse 的使用方法有多种,第一种是在 Chrome 的 DevTools 中选择 Lighthouse 面板,不过要使用的话,得安装代理。
  另一种使用方法是将[Lighthouse](https://github.com/GoogleChrome/lighthouse)下载到本地,安装后使用命令来执行测试,如下所示。
~~~
lighthouse https://www.pwstrick.com --output html --output-path ./report/report.html
~~~
  Lighthouse 给出一些切实可行的优化建议,如下图所示,在每条建议中,还会给出 Learn More 的链接,了解更多优化细节。
:-: ![](https://img.kancloud.cn/08/85/08853526990015987614e7c5fee5fab3_1574x1264.png =800x)
  虽然是英文的,但用[翻译软件](https://www.deepl.com/zh/translator)或自己阅读都比较容易理解其含义。
  例如修改图像格式、压缩图像、预加载影响 LCP 的图像、延迟加载屏幕外的图像、减少未使用的脚本、剔除阻塞渲染的资源等。
  2023-07-21 在阶段时序图列表中,增加性能测试链接,用于[Lighthouse](https://github.com/GoogleChrome/lighthouse)的测试。
:-: ![](https://img.kancloud.cn/54/ae/54ae5405fc5c84400095ff3c1f30b3d3_2934x336.png =800x)
  Lighthouse 可以作为一个库,在 Node.js 中使用,借助其能力,可以做些自定义的性能测试。
  Chrome 浏览器中默认有一栏是 Lighthouse,可用于测试,但是很奇怪,我每次测试页面,得到的分值都很低,用移动网络访问又很快。
  在编写代码时发现,有可能是 Lighthouse 默认的网络限制导致的评分不准,默认[4G](https://github.com/GoogleChrome/lighthouse/blob/main/core/config/constants.js)的下载速度才 1.6Kbps,比我们国内网络慢很多。
~~~
mobileSlow4G: {
rttMs: 150,
throughputKbps: 1.6 * 1024,
requestLatencyMs: 150 * DEVTOOLS_RTT_ADJUSTMENT_FACTOR,
downloadThroughputKbps: 1.6 * 1024 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
uploadThroughputKbps: 750 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
cpuSlowdownMultiplier: 4,
},
~~~
  所以在自己服务器中做测试时,对该限制做了些放宽,下面是完整的接口代码,利用 puppeteer 和 lighthouse 两个库的能力。
~~~
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
const config = require('config');
const domain = config.get('domain');
const lighthouse = require('lighthouse');
const crypto = require('crypto');
router.get("/test/lighthouse", async (ctx) => {
const { url, redirect } = ctx.query;
const options = {
args: ["--no-sandbox", "--disable-setuid-sandbox"]
};
// 使用 puppeteer 连接到浏览器
const browser = await puppeteer.launch(options);
const key = crypto.createHash("md5").update(url).digest("hex");
// 使用 Lighthouse 进行性能测试
const { lhr, report } = await lighthouse(url, {
port: new URL(browser.wsEndpoint()).port,
output: "html",
logLevel: "info",
throttling: {
throughputKbps: 100 * 1024
}
});
const savePath = path.resolve(__dirname, "../static/lighthouse");
// !fs.existsSync(savePath) && fs.mkdirSync(savePath, { recursive: true });
const resultUrl = savePath + `/${key}.html`;
// 将报告内容写入到文件中
fs.writeFileSync(resultUrl, report);
await browser.close();
const redirectUrl = `${domain}/static/lighthouse/${key}.html`;
// 若需要跳转
if (redirect) {
ctx.status = 302; // 临时跳转
ctx.redirect(redirectUrl);
return;
}
ctx.body = {
code: 0,
data: {
url: redirectUrl,
score: lhr.categories.performance.score * 100
}
};
});
~~~
  在本地调试时,非常顺利,但是在部署到服务器上时,遇到了很多麻烦,不是报错,就是在连接浏览器时卡住,整整困扰了我 3 天。
  网上有很多解决方案,例如安装[各种依赖](https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#chrome-doesnt-launch-on-linux),如下所示。
~~~
apt install chromium-browser
apt install libxkbcommon0
....
~~~
  在 puppeteer 启动时,增加 executablePath 参数,指定 chromium-browser 的位置。
~~~
const options = {
args: ["--no-sandbox", "--disable-setuid-sandbox"],
executablePath: '/usr/bin/chromium-browser'
};
~~~
  在项目根目录增加 puppeteer.config.js 配置文件,指定 cacheDirectory 目录。
~~~
const { join } = require("path");
const config = {
cacheDirectory: join(__dirname, ".cache", "puppeteer")
};
/**
* @type {import("puppeteer").Configuration}
*/
module.exports = config;
~~~
  虽然按照网上方案进行了优化,但是都无济于事,最后是将乌班图版本从 16.04 升级至 20.04 才彻底解决了问题。
## 总结
  数据分析和实验室分析是性能优化的两块重要组成部分。
  数据分析在采集到性能信息后,会先进行日志存储,然后按指定的百分位数进行数据整理,最后还会定期进行删除。
  在管理界面提供了几种视图来更好的分析性能瓶颈,包括资源瀑布图、堆叠柱状图和阶段时序图。
  在实验室分析中,主要介绍了 3 款性能测试工具,包括 DevTools Performance、WebPageTest 和 Lighthouse。
  3 款软件都非常优秀,可以帮助开发人员更快、更准的进行优化工作。
*****
> 原文出处:
[博客园-前端性能精进](https://www.cnblogs.com/strick/category/2267607.html)
[知乎专栏-前端性能精进](https://www.zhihu.com/column/c_1610941255021780992)
已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎阅读。
![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、扩展运算符和剩余参数
- 3、解构
- 4、模板字面量
- 5、对象字面量的扩展
- 6、Symbol
- 7、代码模块化
- 8、数字
- 9、字符串
- 10、正则表达式
- 11、对象
- 12、数组
- 13、类型化数组
- 14、函数
- 15、箭头函数和尾调用优化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、类
- 21、类的继承
- 22、Promise
- 23、Promise的静态方法和应用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基础实践
- 3、WebRTC视频通话
- 4、Web音视频基础
- CSS进阶
- 1、CSS基础拾遗
- 2、伪类和伪元素
- 3、CSS属性拾遗
- 4、浮动形状
- 5、渐变
- 6、滤镜
- 7、合成
- 8、裁剪和遮罩
- 9、网格布局
- 10、CSS方法论
- 11、管理后台响应式改造
- React
- 1、函数式编程
- 2、JSX
- 3、组件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表单
- 8、样式
- 9、组件通信
- 10、高阶组件
- 11、Redux基础
- 12、Redux中间件
- 13、React Router
- 14、测试框架
- 15、React Hooks
- 16、React源码分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基础
- 4、webpack进阶
- 5、Git
- 6、Fiddler
- 7、自制脚手架
- 8、VSCode插件研发
- 9、WebView中的页面调试方法
- Vue.js
- 1、数据绑定
- 2、指令
- 3、样式和表单
- 4、组件
- 5、组件通信
- 6、内容分发
- 7、渲染函数和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、数据类型
- 2、接口
- 3、类
- 4、泛型
- 5、类型兼容性
- 6、高级类型
- 7、命名空间
- 8、装饰器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系统和网络
- 3、命令行工具
- 4、自建前端监控系统
- 5、定时任务的调试
- 6、自制短链系统
- 7、定时任务的进化史
- 8、通用接口
- 9、微前端实践
- 10、接口日志查询
- 11、E2E测试
- 12、BFF
- 13、MySQL归档
- 14、压力测试
- 15、活动规则引擎
- 16、活动配置化
- 17、UmiJS版本升级
- 18、半吊子的可视化搭建系统
- 19、KOA源码分析(上)
- 20、KOA源码分析(下)
- 21、花10分钟入门Node.js
- 22、Node环境升级日志
- 23、Worker threads
- 24、低代码
- 25、Web自动化测试
- 26、接口拦截和页面回放实验
- 27、接口管理
- 28、Cypress自动化测试实践
- 29、基于Electron的开播助手
- Node.js精进
- 1、模块化
- 2、异步编程
- 3、流
- 4、事件触发器
- 5、HTTP
- 6、文件
- 7、日志
- 8、错误处理
- 9、性能监控(上)
- 10、性能监控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 监控系统
- 1、SDK
- 2、存储和分析
- 3、性能监控
- 4、内存泄漏
- 5、小程序
- 6、较长的白屏时间
- 7、页面奔溃
- 8、shin-monitor源码分析
- 前端性能精进
- 1、优化方法论之测量
- 2、优化方法论之分析
- 3、浏览器之图像
- 4、浏览器之呈现
- 5、浏览器之JavaScript
- 6、网络
- 7、构建
- 前端体验优化
- 1、概述
- 2、基建
- 3、后端
- 4、数据
- 5、后台
- Web优化
- 1、CSS优化
- 2、JavaScript优化
- 3、图像和网络
- 4、用户体验和工具
- 5、网站优化
- 6、优化闭环实践
- 数据结构与算法
- 1、链表
- 2、栈、队列、散列表和位运算
- 3、二叉树
- 4、二分查找
- 5、回溯算法
- 6、贪心算法
- 7、分治算法
- 8、动态规划
- 程序员之路
- 大学
- 2011年
- 2012年
- 2013年
- 2014年
- 项目反思
- 前端基础学习分享
- 2015年
- 再一次项目反思
- 然并卵
- PC网站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端学习之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 日志
- 2020