- 测试
- 性能剖析
- 指标监控
【免费下载链接】sitespeed.io
sitespeed.io is an open-source tool for comprehensive web performance analysis, enabling you to test, monitor, and optimize your website’s speed using real browsers in various environments.
导读
本文以 sitespeed.io 6.0 发布前的技术预告为切入点,系统梳理该版本围绕「插件系统重构」所做的核心工作:消息队列驱动的插件通信机制、sitespeedio.setup全新队列阶段、HTML 输出可插拔化、基于 filterRegistry 的指标过滤、Docker 默认开启视频与 SpeedIndex 等面向初学者的改进,以及与 Browsertime 2.0、The Coach 1.0、PageXray 2.0 的协同演进。读完本文,你将理解 sitespeed.io 的插件如何通过队列彼此对话、如何为插件生成 HTML 页面与自定义 CSS,并能依据当前仓库源码定位这些机制的实现位置,为开发自己的插件打下基础。
一、6.0 预告发布的技术背景
2017 年 11 月,sitespeed.io 团队在 5.0 发布半年后,于 docs/_posts/2017-11-02-sitespeed.io-6.0-coming-soon.md 发布了 6.0 的预告(当时仓库版本号现为 42.7.0,见 package.json,但插件机制的设计脉络自 6.0 起延续至今)。该文档明确列出了 6.0 的改进清单,核心围绕以下方向:
- 使用 Chartist 展示视觉进度、页面大小与请求数,降低数据阅读门槛;
- HTML 页面适配更大屏幕(更大分辨率下的响应式展示);
- 升级为官方 Graphite Docker 容器并以 Graphite 1.X 为默认;
- 在日志中输出结果的完整 URL,便于把日志与结果文件对应起来;
- 重构插件体系:在上下文(context)中暴露 messageMaker 供插件发送队列消息、暴露 filterRegistry 供插件注册指标以便被 Graphite/InfluxDB 等采集,并把核心功能收敛到 core 目录;
- Docker 运行默认开启
--video与--speedIndex,降低初学者上手门槛; - 支持插件生成 HTML(按 run 或按 page summary);
- 支持插件通过发送
html.css消息覆盖/追加 CSS; - 文档全面重写;
- The Coach 1.0 发布,针对 Google Analytics 与 Google Tag Manager 的建议做了调整。
随后的正式发布文档 docs/_posts/2017-11-27-sitespeed.io-6.0.md 确认:6.0 把 Node.js 从 6 升级到 8(并同步带动 Browsertime 2.0、PageXray 2.0、The Coach 1.0),核心聚焦于插件结构更新——让插件之间可以通过队列通信。
下文将逐一深入讲解这些改进在仓库源码中的实际形态。
二、消息队列:插件间通信的基石
2.1 一切皆消息
6.0 最根本的架构变化是让「插件彼此通过队列通信」。在 lib/core/queue.js 中,createQueue()实现了一个支持并发限制的队列:
queue.limit({ concurrency })设置并发度;queue.process(fn)注册每条消息的处理函数;queue.enqueued / processingStarted / processingEnded / drained提供生命周期回调;queue.isDrained在任务清空时返回true。
在 lib/core/queueHandler.js 中,QueueHandler.createQueues()会为每个实现processMessage的插件创建一条独立队列,并为每条消息记录排队耗时与处理耗时(registerQueueTime/registerProcessingTime,实现于 lib/core/queueStatistics.js),供--queueStats输出统计。消息通过postMessage()广播给所有插件队列,每个插件在自己的队列中依次消费。
每条消息的结构由 lib/support/messageMaker.js 定义:
export function messageMaker(source) { return { make(type, data, extras) { const stamp = timestamp().format(); const uuid = randomUUID(); return merge({ uuid, type, timestamp: stamp, source, data }, extras); } }; }即消息至少包含:
- uuid:全局唯一标识;
- type:消息类型,插件据此判断是否感兴趣(如
browsertime.run、pagexray.pageSummary、html.pug); - timestamp:创建时间;
- source:消息来源(产生消息的插件名);
- data:携带的数据;
- extras:额外字段(如
url、group),可自由扩展。
2.2 运行阶段与sitespeedio.setup
lib/core/queueHandler.js 的run()方法是整个执行流程的骨架,注释清晰地定义了各个阶段:
setup - 插件之间互相通信、做各自所需的初始化 url - 传入待分析的 URL summarize - 全部分析完成后汇总数据 prepareToRender - 渲染前的准备 render - 插件把数据写入磁盘 final - 退出前的收尾(如上传到 S3)对应的调用链为:
this.startProcessingQueues() .then(() => this.postMessage(make('sitespeedio.setup'))) .then(() => this.drainAllQueues()) .then(async () => { for (let source of sources) { await source.findUrls(this, this.options); } }) .then(() => this.drainAllQueues()) .then(() => this.postMessage(make('sitespeedio.summarize'))) .then(() => this.drainAllQueues()) .then(() => this.postMessage(make('sitespeedio.prepareToRender'))) .then(() => this.drainAllQueues()) .then(() => this.postMessage(make('sitespeedio.render'))) .then(() => this.drainAllQueues())其中sitespeedio.setup是 6.0 引入的全新队列阶段:它是第一条广播给所有插件的消息,插件收到后即可告知其他插件自己已就绪、并向它们发送配置。例如:
- lib/plugins/browsertime/index.js 在收到
sitespeedio.setup时回复browsertime.setup,通知其他插件「Browsertime 已就绪,可以给我发送配置」; - lib/plugins/graphite/index.js 同样在
sitespeedio.setup阶段回复graphite.setup; - 插件可以在该阶段向 HTML 插件发送
html.pug(注册模板)、向 Browsertime 发送browsertime.scripts(注入 JavaScript),详见下文。
此外,lib/sitespeed.js 中构造的context对象把messageMaker、filterRegistry、statsHelpers、storageManager、resultUrls、getLogger等统一暴露给插件——这正是 6.0 预告中「把 messageMaker 与 filterRegistry 暴露到 context」的具体落地。插件加载逻辑在 lib/core/pluginLoader.js:默认插件集、--plugins.add/--plugins.remove/--plugins.list的增删查都在此处理,并且支持从内置目录、相对路径、npm 包名、全局安装包四种方式加载插件。
提示:
messageMaker与queueHandler的完整调用链,可以结合 lib/sitespeed.js 阅读——它是整个运行流程的入口,先解析插件名、加载插件、调用每个插件的open(context, options),再进入queueHandler.run(urlSources),最后调用close(options, errors)。
三、让插件生成 HTML:html.pug 与 html.css
3.1 插件如何注册 Pug 模板
6.0 的另一大能力是「插件可以生成 HTML」。机制是:插件在sitespeedio.setup阶段向 HTML 插件发送html.pug消息,HTML 插件随后会把该插件的指标数据渲染成独立的 Tab。
html.pug消息需要携带四个字段:
| 字段 | 说明 |
|---|---|
id | 插件唯一标识,用于拼装消息类型(如gpsi→gpsi.pageSummary) |
name | 在 Tab 上显示的名称(友好名) |
pug | Pug 模板内容(字符串) |
type | run/pageSummary/summary:数据是按每次运行、每页汇总还是全局汇总展示 |
发送示例(对应 docs/documentation/sitespeed.io/plugins/index.md 中的官方示例):
case 'sitespeedio.setup': { queue.postMessage( make('html.pug', { id: 'gpsi', name: 'GPSI', pug: this.pug, type: 'pageSummary' }) ); break; }HTML 插件收到html.pug后的处理在 lib/plugins/html/index.js 中:
case 'html.pug': { // we got a pug from plugins, let compile and cache them addTemplate(message.data.id, message.data.pug); // and also keep the types so we can render them this.HTMLBuilder.addType( message.data.id, message.data.name, message.data.type ); // make sure we pickup the data for this pug this.collectDataFrom.push(message.data.id + '.' + message.data.type); break; }addTemplate在 lib/plugins/html/renderer.js 中实现——用pug包的compile()把模板字符串编译并缓存,模板文件名就是id + '.pug'。同时HTMLBuilder.addType()(见 lib/plugins/html/htmlBuilder.js)根据type把模板挂到 run 页、pageSummary 页或 summary 页的 Tab 列表,并把id.type追加进collectDataFrom——HTML 插件会自动收集所有形如*.run、*.pageSummary的消息数据,按 URL 归位(dataCollector.addDataForUrl)。
插件发送数据时,只需用id + type拼出消息类型并带上 URL:
queue.postMessage( make('gpsi.pageSummary', result, { url, group }) );3.2 插件如何追加 CSS
如果插件希望覆盖或追加 HTML 报告样式,只需发送类型为html.css的消息。HTML 插件在 lib/plugins/html/index.js 中通过HTMLBuilder.addInlineCSS(message.data)接收,并在渲染时把累积的 CSS 以内联样式合入最终页面(见 lib/plugins/html/htmlBuilder.js 中this.inlineCSS.join(''))。
这一机制让第三方插件无需修改 HTML 插件源码即可定制报告的视觉呈现,是 6.0 预告中「插件可覆盖/追加 CSS」的直接实现。
3.3 默认注册的数据类型
HTML 插件在open()中注册了一组默认会收集的数据类型(见 lib/plugins/html/index.js):
this.collectDataFrom = [ 'browsertime.run', 'browsertime.console', 'browsertime.pageSummary', 'pagexray.run', 'pagexray.pageSummary', 'coach.run', 'coach.pageSummary', 'thirdparty.run', 'thirdparty.pageSummary', 'crux.pageSummary' ];这些类型的消息会被自动收集、按 URL 归档,最终由sitespeedio.render消息触发的HTMLBuilder.render()渲染成 HTML 报告。插件新增类型只需在sitespeedio.setup阶段注册html.pug,其id.type会被自动追加到该列表。
四、让插件通过 Browsertime 采集指标
6.0 的另一项重要能力是「插件可以在被测页面上运行 JavaScript 采集指标」。Browsertime 插件在 lib/plugins/browsertime/index.js 中监听两类消息:
browsertime.scripts:同步脚本,插件需提供category与scripts(脚本名到脚本体的映射);browsertime.asyncscripts:异步脚本,结构相同。
插件在sitespeedio.setup阶段发送脚本:
case 'sitespeedio.setup': { queue.postMessage( make('browsertime.scripts', { category: 'yourplugin', scripts: { userAgent: '(function() {return navigator.userAgent;})();', title: '(function() {return document.title;})();' } }) ); break; }Browsertime 在页面加载时执行这些脚本,并把结果合并进browsertime.run消息的数据中;插件监听browsertime.run即可取回:
case 'browsertime.run': { console.log(message.data.yourplugin); break; }在 Pug 模板中可通过pageInfo.data.browsertime.run.yourplugin.title访问。注意browsertime.scripts消息要求同时携带category与scripts字段,否则 Browsertime 插件会记录格式错误的日志(见 lib/plugins/browsertime/index.js 中对message.data.category && message.data.scripts的校验)。
从源码看,Browsertime 插件还会在sitespeedio.setup阶段广播browsertime.config(若开启截图,通知 HTML 插件截图类型)与browsertime.alias(URL 别名/分组),Graphite 插件也会监听browsertime.setup来决定是否采集browsertime.pageSummary的注解数据——这些都是一次sitespeedio.setup广播在多个插件间产生的联动效应。
五、filterRegistry:决定哪些指标进入时序数据库
6.0 预告中提到「暴露 filterRegistry 到 context,让插件注册应被 Graphite/InfluxDB 等采集的指标」。该机制实现在 lib/support/filterRegistry.js:
registerFilterForType(filter, type):为某消息类型注册指标白名单;addFilterForType(filter, type):追加过滤规则;getFilterForType(type)/getFilters()/getTypes():查询已注册的过滤规则;filterMessage(message):对消息按注册的过滤规则裁剪数据(调用 lib/support/metricsFilter.js 的filterMetrics)。
Browsertime 插件在open()中即为三类消息注册了默认过滤规则(见 lib/plugins/browsertime/index.js):
context.filterRegistry.registerFilterForType( DEFAULT_METRICS_PAGE_SUMMARY, 'browsertime.pageSummary' ); context.filterRegistry.registerFilterForType( DEFAULT_METRICS_SUMMARY, 'browsertime.summary' ); context.filterRegistry.registerFilterForType( this.options.limitedRunData ? DEFAULT_METRICS_RUN_LIMITED : DEFAULT_METRICS_RUN, 'browsertime.run' );这些默认指标清单定义在 lib/plugins/browsertime/default/ 目录下(metricsPageSummary.js、metricsSummary.js、metricsRun.js、metricsRunLimited.js)。Graphite 插件在 lib/plugins/graphite/index.js 中持有context.filterRegistry,并通过--graphite.messages配置决定采集哪些消息类别;自定义插件只需在open()中调用context.filterRegistry.registerFilterForType(...),即可让自己的指标进入 Graphite/InfluxDB 等时序数据库的采集范围。
六、Docker 默认开启视频与 SpeedIndex
6.0 预告特别指出「Running Docker adds --video and --speedIndex by default to make it easier for beginners」。在 lib/cli/options/video.js 中可以看到这些参数当前的形态与别名:
--browsertime.visualMetrics(别名--visualMetrics、--speedIndex):计算 SpeedIndex、First Visual Change、Last Visual Change 等视觉指标,依赖 FFMpeg 与 Python;--browsertime.video(别名--video):录制并保存视频;要完全关闭需同时设置video与visualMetrics为false;--browsertime.videoParams.framerate(别名--fps,默认 30):帧率——这正是 Browsertime 2.0 中「默认帧率从 60 降到 30」的体现;--browsertime.videoParams.convert(默认true):把原始视频转换为通用可播放格式;--browsertime.videoParams.createFilmstrip(默认true)、--browsertime.videoParams.filmstripQuality(默认 75)、--browsertime.videoParams.filmstripFullSize(默认false):胶片条(filmstrip)截图开关、质量与全尺寸选项;--browsertime.videoParams.addTimer(默认true):在视频上叠加计时器与指标。
当前的 Dockerfile 中设置了ENV SITESPEED_IO_BROWSERTIME__XVFB=true与SITESPEED_IO_BROWSERTIME__DOCKER=true,容器入口为 docker/scripts/start.sh,它负责启动 ADB(如需 Android 调试)、以正确的用户身份运行sitespeed.io,并支持REPLAY模式(WebPageReplay 录制/回放,配合 docker/webpagereplay/ 下的 wpr 二进制与证书)。这意味着在 Docker 内运行测试时,视频与视觉指标相关的环境(FFMpeg、Xvfb、Python)已经就绪,初学者无需手动配置。
相关视频参数在 Browsertime 2.0 发布文档中的说明(docs/_posts/2017-11-27-sitespeed.io-6.0.md):
- 默认帧率改为 30(
--videoParams.framerate),以适配小型云主机; - 视频录制分两步:先无损录制、再转换为通用格式,降低 ffmpeg 的 CPU 占用并使指标更稳定;
- 可用
--videoParams.combine把 preScript/postScript 的执行过程并入视频,方便调试脚本。
七、Graphite 1.X 与官方容器
6.0 还升级了默认的 Graphite 部署:改用官方 Graphite Docker 容器、以 Graphite 1.X 为默认。当前仓库的 docker/docker-compose.yml 使用sitespeedio/graphite:1.1.10-3镜像,暴露 2003(carbon 接收端口)与 8080(Graphite Web);Grafana 数据源通过 docker/grafana/provisioning/datasources/graphite.yaml 以 proxy 方式接入http://graphite:80,并默认匿名访问开启。
配套的 Graphite 配置(carbon 存储周期、聚合规则等)位于 docker/graphite/conf/ 下(carbon.conf、storage-schemas.conf、storage-aggregation.conf),docker-compose 注释中明确提示生产环境应将 whisper 数据与 graphite.db 挂载到容器外持久化。
Graphite 插件的采集逻辑在 lib/plugins/graphite/index.js:支持--graphite.host、--graphite.port、--graphite.namespace、--graphite.messages、--graphite.bulkSize、--graphite.sendAnnotation、--graphite.addSlugToKey等配置(缺少 host 时会直接抛错),并支持 StatsD 模式(isStatsD判定,对应 lib/plugins/graphite/helpers/is-statsd.js)。发送逻辑由 lib/plugins/graphite/graphite-sender.js 与 lib/plugins/graphite/statsd-sender.js 实现。
八、The Coach 1.0 与 PageXray 2.0
6.0 系列发布还同步推进了生态工具:
- The Coach 1.0:Coach 是基于 HAR 的网页最佳实践建议引擎(
coach-core)。1.0 版本针对 Google Analytics 与 Google Tag Manager 的建议做了调整,让作为「独立工具」的 Coach 对这两大第三方脚本给出更准确的评分。当前仓库中 Coach 数据由 lib/plugins/coach/ 收集,核心引擎来自coach-core依赖(见 package.json 的 dependencies)。 - PageXray 2.0:以 bug 修复与升级 NodeJS 8 为主。PageXray 负责解析 HAR、生成按域名/内容类型的资源统计,其插件实现位于 lib/plugins/pagexray/,DOM 层面则由 lib/plugins/domains/ 处理按域名汇总。
注意:以上描述均以当前仓库的实际状态为准——仓库已演进到 42.7.0,6.0 时期的细节以发布文档与源码交叉印证为准,具体参数请以你正在使用的版本为准。
九、从 6.0 到现在的插件开发路径
6.0 奠定的消息队列架构延续至今。当前开发插件遵循 docs/documentation/sitespeed.io/plugins/index.md 的指南:插件继承@sitespeed.io/plugin提供的SitespeedioPlugin基类,实现constructor(options, context, queue)、open(context, options)、processMessage(message, queue)、close(options, errors),并通过super.sendMessage(...)/queue.postMessage(...)收发消息。
9.1 插件的生命周期
- open():启动时调用一次,可获取
context与options,做初始化(如注册 filterRegistry、准备存储); - processMessage():每条队列消息都会经过插件,插件按
message.type决定是否处理; - close():全部 URL 分析完成后调用一次,用于收尾(如关闭资源)。
9.2 重要的内置消息
sitespeedio.setup:第一条消息,插件在此向其他插件广播自身能力/配置;sitespeedio.summarize:所有 URL 分析完毕,插件需汇总指标(产出*.summary);sitespeedio.render:渲染(写盘)时刻,HTML 插件在此渲染报告并广播html.finished;*.run/*.pageSummary/*.summary:按运行、按页面汇总、按全部页面汇总的三级数据消息。
插件的加载机制(lib/core/pluginLoader.js)支持四种方式:
# 内置/本地目录 --plugins.add /sitespeed.io/myplugin # 相对路径 --plugins.add ../my/super/plugin # npm 包名(本地或全局安装) npm install @sitespeed.io/plugin-lighthouse -g --plugins.add @sitespeed.io/plugin-lighthouse # 预烘焙 Docker 镜像(Dockerfile 中设置 SITESPEED_IO_PLUGINS__ADD)9.3 让插件指标进入性能预算
插件还可以把自己的指标交给 budget 插件做预算检查:在sitespeedio.setup阶段发送budget.addMessageType消息声明要采集的消息类型,例如:
queue.postMessage(make('budget.addMessageType', {type: 'gpsi.pagesummary'}));十、验证你的理解:源码阅读路径
如果你想亲自验证上述机制,可按以下路径阅读当前仓库:
- lib/sitespeed.js —— 运行入口:解析插件、构建 context、编排整个队列流程;
- lib/core/queue.js 与 lib/core/queueHandler.js —— 队列实现与
setup → url → summarize → prepareToRender → render阶段编排; - lib/support/messageMaker.js 与 lib/support/filterRegistry.js —— 消息构造与指标过滤;
- lib/plugins/html/index.js —— 如何消费
html.pug、html.css并收集*.run/*.pageSummary数据; - lib/plugins/browsertime/index.js —— 如何消费
browsertime.scripts/browsertime.asyncscripts、响应sitespeedio.setup; - lib/plugins/graphite/index.js —— 如何利用 filterRegistry 与
sitespeedio.setup完成指标落库; - lib/core/pluginLoader.js —— 默认插件集与
--plugins.add/remove/list的解析逻辑。
结语
sitespeed.io 6.0 预告文档所勾勒的改进——消息队列驱动的插件体系、可生成 HTML 的插件、可注入的 CSS、Docker 默认视频与 SpeedIndex、Graphite 官方容器、Coach 1.0——在近十年后的当前仓库源码中依然清晰可辨,并构成了整个插件生态的基石。理解sitespeedio.setup这一条消息如何串联起 Browsertime、HTML、Graphite 等插件之间的协作,你就掌握了为 sitespeed.io 编写可复用、可共享的第三方插件所需的核心心智模型。
- 测试
- 性能剖析
- 指标监控
【免费下载链接】sitespeed.io
sitespeed.io is an open-source tool for comprehensive web performance analysis, enabling you to test, monitor, and optimize your website’s speed using real browsers in various environments.
相关推荐
从0到1构建Boxed风格Web应用:最佳架构实践与代码示例
从0到1构建Boxed风格Web应用:最佳架构实践与代码示例 Boxed Framework是一个强大的.NET Core扩展和辅助NuGet包集合,专为简化现
Tiled核心架构解析:从libtiled到插件系统
Tiled核心架构解析:从libtiled到插件系统 本文深入解析了Tiled地图编辑器的核心架构,从基础库libtiled的设计到完整的插件系统实现。文章详细
游戏开发桌面应用开发工具Spyder 6 系列演进全解析:从 6.0 架构重构到 6.2 现代化特性
Spyder 6 系列演进全解析:从 6.0 架构重构到 6.2 现代化特性 本文以仓库内 changelogs/Spyder 6.md https://lin
开发工具IDE代码编辑器
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考