Perf-patronus
Perf-patronus 会默认监控以下性能指标:
-
FCP
-
Total Blocking Time
-
First CPU Idle
-
TTI
-
Speed Index
-
LCP
工作架构和流程
-
特定环境完成 MR 部署后,开始进行性能体检服务。
-
性能体检服务由消息队列 worker 消费完成。
-
每一次性能体检产出体检数据,根据数据内容是否达标,进行后续消息提醒;体检数据内容同时被性能守卫系统平台所消费,展现相关页面的性能情况。
-
性能数据由 Redis 存储。
-
性能体检相关富媒体资源(页面截图等)可以由容器持久化目录存储,或上传到 OSS 服务
如何真实反映用户情况?
-
**页面不确定性:**比如 A/B 实验情况。这种情况性能体检服务无法进行处理,需要接入者保证页面性能的可对比性。
-
**用户侧网络情况不确定性:**性能体检服务应该设计有可靠的 Throttling 机制,以及较合理的请求等待时间。
-
**终端设备不确定性:**性能体检服务应该设计有可靠的 CPU Simulating 能力,并统一 CPU 能力测试范围标准。
-
性能体检服务的稳定性
整体流程
async run(runOptions: RunOptions) {
// 检查相关数据
const results = {};
// 使用 Puppeteer 创建一个无头浏览器
const context = await this.createPuppeteer(runOptions);
try {
// 执行必要的登录流程
await this.Login(context);
// 页面打开前的钩子函数
await this.before(context);
// 打开页面,获取 lighthouse 数据
await this.getLighthouseResult(context);
// 页面打开后的钩子函数
await this.after(context, results);
// 收集页面性能数据
return await this.collectArtifact(context, results);
} catch (error) {
throw error;
} finally {
// 关闭页面和无头浏览器
await this.disposeDriver(context);
}
}
其中,创建一个 Puppeteer 无头浏览器的逻辑,如下代码:
async createPuppeteer(runOptions: RunOptions) {
// 启动配置项可以参考 puppeteerlaunchoptions
const launchOptions: puppeteer.LaunchOptions = {
headless: true, // 是否采用无头模式
defaultViewport: {
width: 1440,
height: 960
}, // 指定页面视口宽高
args: ['--no-sandbox', '--disable-dev-shm-usage'],
// Chromium 安装路径
executablePath: 'xxx',
};
// 创建一个浏览器对象
const browser = await puppeteer.launch(launchOptions);
const page = (await browser.pages())[0];
// 返回浏览器和页面对象
return {
browser,
page
};
}
打开相关页面,并执行 Lighthouse 模块,如下代码所示:
async getLighthouseResult(context: Context) {
// 获取上下文信息,包括 browser 和页面地址
const {
browser,
url
} = context;
// 使用 lighthouse 模块进行性能采集
const {
artifacts,
lhr
} = await lighthouse(url, {
port: new URL(browser.wsEndpoint()).port,
output: 'json',
logLevel: 'info',
emulatedFormFactor: 'desktop',
throttling: {
rttMs: 40,
throughputKbps: 10 * 1024,
cpuSlowdownMultiplier: 1,
requestLatencyMs: 0,
downloadThroughputKbps: 0,
uploadThroughputKbps: 0,
},
disableDeviceEmulation: true,
// 只检测 performance 模块
onlyCategories: ['performance'],
});
// 回填数据
context.lhr = lhr;
context.artifacts = artifacts;
}
上述流程都是常规启用 Lighthouse 模块,在 Node.js 环境中对相关页面执行 Lighthouse 的逻辑。
我们自定义的逻辑往往可以通过 Lighthouse 插件实现,一个 Lighthouse 插件就是一个 Node.js 模块,在插件中我们可以定义 Lighthouse 的检查项,并在产出报告中以一个新的 category 呈现。
举个例子,我们想要实现“检查页面中是否含有大小超过 5MB 的 GIF 图片”的任务,如以下代码
module.exports = {
// 对应 audits
audits: [{
path: 'lighthouse-plugin-cinememe/audits/cinememe.js',
}],
// 该 plugin 对应的 category
category: {
title: 'Obligatory Cinememes',
description: 'Modern webapps should have cinememes to ensure a positive ' +
'user experience.',
auditRefs: [
{
id: 'cinememe',
weight: 1
},
],
},
};
对应自定义 Audits,如下代码:
'use strict';
const Audit = require('lighthouse').Audit;
// 继承 Audit 类
class CinememeAudit extends Audit {
static get meta() {
return {
id: 'cinememe',
title: 'Has cinememes',
failureTitle: 'Does not have cinememes',
description: 'This page should have a cinememe in order to be a modern ' +
'webapp.',
requiredArtifacts: ['ImageElements'],
};
}
static audit(artifacts) {
// 默认 hasCinememe 为 false(大小超过 5MB 的 GIF 图片)
let hasCinememe = false;
// 非 Cinememe 图片结果
const results = [];
// 过滤筛选相关图片
artifacts.ImageElements.filter(image => {
return !image.isCss &&
image.mimeType &&
image.mimeType !== 'image/svg+xml' &&
image.naturalHeight > 5 &&
image.naturalWidth > 5 &&
image.displayedWidth &&
image.displayedHeight;
}).forEach(image => {
if (image.mimeType === 'image/gif' && image.resourceSize >= 5000000) {
hasCinememe = true;
} else {
results.push(image);
}
});
const headings = [
{
key: 'src',
itemType: 'thumbnail',
text: ''
},
{
key: 'src',
itemType: 'url',
text: 'url'
},
{
key: 'mimeType',
itemType: 'text',
text: 'MIME type'
},
{
key: 'resourceSize',
itemType: 'text',
text: 'Resource Size'
},
];
return {
score: hasCinememe > 0 ? 1 : 0,
details: Audit.makeTableDetails(headings, results),
};
}
}
module.exports = CinememeAudit;
通过上面插件,我们就可以在 Node.js 环境中,结合 CI/CD 流程,找出页面中大小超过 5MB 的 GIF 图片了。
由插件原理可知,一个性能守卫系统,是通过常规插件和自定义插件集合而成的,具有良好的扩展性。